Agent lambda development
Here is agent lambda development description.
More information about lambda types and differences of agent and others available at lambda types description.
More information about LUNA videoanalytics, in particular, luna-video-manager, video-agents and their interaction see its’ developers manuals.
Agent lambda requirements
Agent lambda has several requirements to addition with basic requirements:
Luna Video Manager available by credentials from Luna-Configurator
Luna Licenses available by credentials from Luna-Configurator
Luna Events available by credentials from Luna-Configurator; it can be disabled using ADDITIONAL_SERVICES_USAGE setting and it this case lambda should provide for work without Luna-Events usage
Luna Sender available by credentials from Luna-Configurator; it can be disabled using ADDITIONAL_SERVICES_USAGE setting and it this case lambda should provide for work without Luna-Sender usage
Luna Faces/Images Samples Store available by credentials from Luna-Configurator; it can be disabled using ADDITIONAL_SERVICES_USAGE setting and it this case lambda should provide for work without Luna-Image-Store usage
Agent lambda configuration
The agent lambda required several settings from luna-configurator, whose can be separated to several groups:
LUNA_LAMBDA_AGENT_UNIT_LOGGER - lambda logger settings
luna-services addresses and timeouts settings (for example, LUNA_EVENTS_ADDRESS and LUNA_EVENTS_TIMEOUTS will be used by lambda to make requests to luna-events service)
ADDITIONAL_SERVICES_USAGE setting will be used to determine which luna-services can be used by the lambda (the lambda will not check connection to disabled services and will raise an error if user try to make request to such service)
LUNA_LAMBDA_AGENT_UNIT_ANALYTICS_SETTINGS - lambda analytics-specific settings, for example, settings of which device must be used by one or another analytics - CPU or GPU
LUNA_LAMBDA_AGENT_UNIT_VIDEO_SETTINGS/LUNA_LAMBDA_AGENT_UNIT_RUNTIME_SETTINGS - lambda video processing settings
Note
Take into account that video-agents can be internal and external (see Luna-Video-Manager/Luna-Video-Agent) developers documentation for details. The lambda video-agent represents internal video-agent at the moment, this behaviour can be changed in the future releases.
Agent lambda development
Lambda agent works with analytics, each lambda agent must have at least one video analytics.
It is possible to specify video analytics as separate package which will installed as lambda dependency or include analytics as module into lambda. The lambda_main.py and other configuration files will not differ in this cases.
The description below describes the case when analytics represents itself separate package, for description in the case of analytics is a module in lambda see analytics development chapter.
The agent lambda development in the easiest way consists of it’s configuration by specifying several parameters in lambda_main.py file.
Warning
The names of analytics, features, etc. listed below are fictitious and have nothing in common with the names of real analytics, etc.
The only required variable which must be specified is a list of available analytics, which also must be specified as lambda requirements, for example:
#: list of analytics for agent lambda
AVAILABLE_ANALYTICS = ["people"]
people_analytics==1.2.0
There are some more parameters which can also be relevant:
The ANALYTICS_LICENSE_FEATURES is a map with analytics and their licensing feature names. If analytics not required licensing, it must not be included into this map. If no one analytics must depends on licensing, this map must be empty or may not be included in lambda_main.py.
The ESTIMATORS_TO_INITIALIZE and ESTIMATORS_TARGETS are lists of SDK estimators and their targets which must initialized before analytics usage. If no one analytics not required SDK estimators usage, this lists must be empty or may not be included in lambda_main.py.
Note
In this example lambda has people_analytics package of version 1.2.0 as requirement, which represents video analytics package with analytics named people. The usage of this analytics is regulated by license feature named people_feature. It is requires the estimator from luna-sdkloop package named people and the people target estimator to be initialized before lambda starts.
from sdk_loop.enums import Estimators, LoopEstimations
#: list of analytics for agent lambda
AVAILABLE_ANALYTICS = ["people"]
#: map with analytics and their license features
ANALYTICS_LICENSE_FEATURES = {"people": "people_feature"}
#: list of estimators for initialization
ESTIMATORS_TO_INITIALIZE = [Estimators.people]
#: list of estimators targets for initialization
ESTIMATORS_TARGETS = [LoopEstimations.people]
people_analytics==1.2.0
There are some more settings which can be used for agent lambda in lambda_main.py file:
# count of video-agent registration attempt in *luna-video-manager*
REGISTRATION_ATTEMPT_COUNT = 5
# delay between registration attempts
REGISTRATION_ATTEMPT_DELAY = 1
# max size of feedback which can be sent to *luna-video-manager* at one time
FEEDBACK_MAX_BATCH_COUNT = 1000
# the code of response for ws-connections when agent shutdown
WSCODE_GOING_AWAY = 1001
# the name of lambda video-agent which will be registered in *luna-video-manager*
AGENT_NAME = "luna-lambda-great-video-agent"
# the description of lambda video-agent which will be registered in *luna-video-manager*
AGENT_DESCRIPTION = "luna-lambda-greatest-video-agent"
The are many other possibilities to modify such lambda agent behavior, it’s interaction with analytics and so on, which can be realized in the same way as regular luna-video-agent, for more information, see luna-video-agent developers manual.
Agent video analytics development
It is possible to specify video analytics as separate package which will installed as lambda dependency or include analytics as module into lambda. The description below describes analytics module, which can then be included as a module or package.
Note
The complete for user video analytics development guide is in development and will be included in future releases.
In presented example(s) the poetry tool using for dependencies management, so the pyproject.toml file is filled by user and the poetry.lock file must be generated using poetry tool.
The example of video analytics which detect suits on video/streams and generate events if suits appears.
Note
The example below uses image classification model (“resnet50-v2-7.onnx”) and json file (“imagenet-simple-labels.json”) from github. To make this example works it needs to get this files from github and place into data folder of lambda agent archive.
├──__init__.py
├──analytics.py
├──classes.py
├──common.py
├──models.py
├──spec.html
├──spec.yml
└──data
├──resnet50-v2-7.onnx
└──imagenet-simple-labels.json
└──suit_nodes
├──__init__.py
├──events.py
├──suit_estimator.py
└──suid_nodes.py
Note
The video analytics requires some dependencies which are available on visionlabs pypi for local run. All provided examples use visionlabs pypi specification at tool.poetry.source section of pyproject.toml file which make possible to install dependencies from the above source. It is also required HASP license provided by VisionLabs. For agent lambda creation, all required video analytics dependencies already included in base image (which is using for agent lambda building).
If it needs to build this analytics as separate package, it must include its’ dependencies for example using pyproject.toml/poetry.lock files:
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "suit-analytics"
version = "0.0.4"
description = "suit analytics"
authors = [ "VisionLabs", ]
[[tool.poetry.source]]
name = "vlabspypi"
url = "http://pypi.visionlabs.ru/root/public/+simple"
priority = "primary"
[[tool.poetry.source]]
name = "public-pypi"
url = "https://pypi.org/simple"
priority = "supplemental"
[tool.setuptools.package-data]
analytics-docs = ["spec.html"]
onnx = ["data/resnet50-v2-7.onnx"]
[tool.poetry.dependencies]
python = "^3.12"
luna-analytics-manager = {version="*", extras=["sdk"]}
opencv-python-headless = "*"
nvidia-curand-cu11 = "*"
nvidia-cufft-cu11 = "*"
onnxruntime-gpu = "*"
pynvcodec = "^2.6.0"
After dependencies resolving it must be built into a packages (for example, using poetry build).
The *__init__.py* module must contains all presented imported objects and classes to make analytics works.
"""Suit analytics."""
from video_analytics_helpers.luna_wraps import EventMetadata, LunaApiExternalParams, LunaWraps as PostProcessing
from .analytics import AnalyticsCtx, FrameCtx, initialize
from .common import getDocumentation, mandatoryNodes, targetNodes
from .models import Callbacks, Params, Targets
The *analytics.py* module contains analytics initialization, frame and analytics contexts.
"""Analytics template."""
from logging import Logger
from pathlib import Path
import attr
import numpy as np
import onnxruntime as ort
from luna_analytics_manager.base import FrameEnviron
from luna_analytics_manager.templates.sdk import SDKAnalyticsCtx, SDKFrameCtx
from lunavl.sdk.image_utils.image import VLImage
from video_analytics_helpers.onnxruntime_wrapper import OnnxruntimeWrapper
from .classes import AnalyticsRes, FrameRes
from .models import Callbacks, Params, Targets
logger = Logger("luna.suit")
async def initialize(dataPath: str | Path | None = None, **kwargs) -> None:
if dataPath is None:
configPrefix = Path(__file__).absolute().parent / "data"
elif isinstance(dataPath, str):
configPrefix = Path(dataPath)
else:
configPrefix = dataPath
configPrefix / "imagenet-simple-labels.json"
runtimeSettings = kwargs.get("runtime_settings", {})
options = ort.SessionOptions()
options.intra_op_num_threads = runtimeSettings.get("num_threads", 6)
backend = (
[("CUDAExecutionProvider", {"device_id": 0})]
if runtimeSettings.get("device_class", "cpu") == "gpu"
else ["CPUExecutionProvider"]
)
globalSession = ort.InferenceSession(
(configPrefix / "resnet50-v2-7.onnx").as_posix(),
sess_options=options,
providers=backend,
)
globalSession.set_providers(backend)
onnxWrapper = OnnxruntimeWrapper(options.intra_op_num_threads, globalSession)
AnalyticsCtx.initialize(onnxWrapper)
@attr.dataclass(slots=True)
class FrameCtx(SDKFrameCtx):
"""Frame processing context."""
processed: FrameRes | None = None
@attr.dataclass(slots=True)
class AnalyticsCtx(SDKAnalyticsCtx):
"""Analytics context."""
name: str = "suit"
params: Params = attr.field(factory=Params)
targets: Targets = attr.field(factory=Targets)
callbacks: Callbacks = attr.field(factory=Callbacks)
aggregated: AnalyticsRes = attr.field(factory=AnalyticsRes)
@classmethod
def initialize(cls, onnxWrapper):
cls.onnxWrapper = onnxWrapper
async def prepareFrameCtx(self, frame: FrameEnviron[np.ndarray, VLImage]) -> FrameCtx:
"""Prepare frame processing context."""
return FrameCtx(
image=frame.frame,
number=frame.raw.number,
timestamp=frame.raw.timestamp,
lastFrame=frame.raw.lastFrame,
)
The *classes.py* module contains frame and analytics results containers description.
"""Analytics structures."""
import attr
from .suit_nodes.events import SuitEvent
@attr.dataclass(slots=True)
class FrameRes:
"""Analytics result on a single frame."""
events: list[SuitEvent]
def asDict(self):
"""Build results in API format."""
return {"events": [event.asDict() for event in self.events]}
@attr.dataclass(slots=True)
class AnalyticsRes:
"""Analytics aggregated result."""
def asDict(self):
"""Build results in API format."""
return {}
The *common.py* module contains analytics target, mandatory nodes and `getDocumentation` function which returns raw html analytics documentation for user.
"""Analytics setup."""
from pathlib import Path
from .models import Params, Targets
from .suit_nodes.suit_nodes import NodeOverview, NodeSuit
NODES = {NodeOverview, NodeSuit}
_DOCS_PATH = Path(__file__).parent / "spec.html"
def targetNodes(targets: Targets):
"""
Pre-select nodes by user-defined targets.
Examle:
nodes = set()
if {"foo", "foobar") & targets:
nodes +| NodeFoo
if "baz" in targets:
nodes +| NodesBaz
yield from nodes
"""
yield from (node for node in NODES if node.name in targets) # type: ignore
def mandatoryNodes(params: Params, targets: Targets):
"""
Pre-select mandatory nodes according to configuration parameters.
Examle:
nodes = set()
if params.baz.filters.threshold is not None and "baz" in targets:
nodes |= {NodeBazFilter}
yield from nodes
"""
yield from (NodeSuit,)
def getDocumentation() -> str:
"""Get analytics documentation"""
with open(_DOCS_PATH) as f:
return f.read()
The *models.py* module contains analytics parameters models.
"""Analytics models."""
from typing import Annotated, Literal
from annotated_types import Ge
from luna_analytics_manager.templates.mixins.frame_frequency_mixins import RateFrame, RateTime
from luna_analytics_manager.templates.models import EventPolicy, WrapModelError, WrapUnionError
from luna_analytics_manager.templates.sdk import SDKAnalyticsTargets
from pydantic import Field, Strict
from video_analytics_helpers.models import EventAnalyticCallback, HttpAnalyticCallback, WSAnalyticCallback
from video_analytics_helpers.params import BaseParams
from vlutils.structures.pydantic import BaseModel
class RateFrameDefault(BaseModel):
period: Annotated[int, Ge(1), Strict()] = 1
unit: Literal["frame"] = "second"
class OnetimeEventPolicyDefault(BaseModel):
trigger: Literal["start", "end"] = "start"
class Params(BaseParams):
"""Params of analytic"""
rate: Annotated[
RateFrame | RateTime,
WrapUnionError,
Field(discriminator="unit", default_factory=RateFrameDefault),
]
droi: None = None
eventPolicy: Annotated[
EventPolicy,
WrapUnionError,
WrapModelError,
Field(default_factory=OnetimeEventPolicyDefault),
]
class Targets(SDKAnalyticsTargets[Literal["suit"]]):
"""Targets of analytic"""
_default = {"suit"}
Callbacks = Annotated[
list[Annotated[HttpAnalyticCallback | WSAnalyticCallback | EventAnalyticCallback, Field(discriminator="type")]],
Field(max_length=10),
]
The *spec.yml* file contains openapi documentation with analytics parameters description in yaml format.
openapi: 3.0.0
info:
version: 'v.0.0.1'
title: 'Suit Analytics'
description: |
`Suit Analytics` is intended for determination of:
- suit_class parameter;
The `suit` analytics decode stream frames taking into account `rate` parameter
from <a href="#tag/suit-analytics/paths/~1suit_analytics/post">stream creation request analytic parameters</a>
and performs the following actions:
- Calculates suit class
- For each callback check it's conditions and execute callbacks, which fit conditions.
The schema of data sending by `callback` described as stream creation request callback.
components:
schemas:
error:
type: object
properties:
error_code:
type: integer
description: Error code.
desc:
type: string
description: Short error description.
detail:
type: string
description: Error details.
link:
type: string
description: Link to the documentation website with the error description.
required: [error_code, detail, desc, link]
example:
error_code: 1
detail: internal server error
desc: internal server error
link: "https://docs.visionlabs.ai/info/luna/troubleshooting/errors-description/code-1"
int01:
type: integer
enum: [0,1]
roi_int_coordinates:
type: integer
minimum: 0
default: 0
maximum: 65536
example: 3327
roi_float_percent:
type: number
format: float
minimum: 0
default: 0.0
maximum: 100.0
example: 87.4
roi_float_percent_size:
type: number
format: float
minimum: 0.00001
maximum: 100.0
example: 66.3
roi_abs:
type: object
properties:
x:
$ref: '#/components/schemas/roi_int_coordinates'
y:
$ref: '#/components/schemas/roi_int_coordinates'
width:
$ref: '#/components/schemas/roi_int_coordinates'
height:
$ref: '#/components/schemas/roi_int_coordinates'
mode:
type: string
enum: [abs]
example: "abs"
description: Coordinates and size are set in pixels.
required: [x, y, width, height, mode]
roi_percent:
type: object
properties:
x:
$ref: '#/components/schemas/roi_float_percent'
y:
$ref: '#/components/schemas/roi_float_percent'
width:
$ref: '#/components/schemas/roi_float_percent_size'
height:
$ref: '#/components/schemas/roi_float_percent_size'
mode:
type: string
enum: [ percent ]
example: "percent"
description: Coordinates and size are set in percentage.
required: [x, y, width, height, mode]
roi:
oneOf:
- $ref: '#/components/schemas/roi_abs'
- $ref: '#/components/schemas/roi_percent'
description: |
Region of interest on a frame. Boundaries of the area are described in `x`, `y` coordinates
of the top left point and `width`, `height` properties
**Region must not be any bigger than the original frame**
The region of interest will be sent to estimator. The smaller the `roi`, the smaller the area the estimator
will process and, accordingly, work faster.
number01:
type: number
minimum: 0
maximum: 1
rate:
type: object
description: |
Analytic rate execution determines on which frames the analytics will be launched.
Analytic rate execution can be configured in next ways:
- execute analytic on each Nth frame (`unit` - `frame`)
- execute analytic on each frame corresponding to the Nth second (`unit` - `second`)
properties:
period:
type: float
minimum: 0
default: 1
description: Period length.
unit:
type: string
default: second
enum:
- frame
- second
description: Unit for a period calculation (every `n` seconds or every `n` frames).
required: [ unit, period ]
callback_base:
type: object
properties:
enable:
type: integer
enum: [0, 1]
default: 1
description: Whether callback enabled or not.
callback_ws:
allOf:
- $ref: "#/components/schemas/callback_base"
- type: object
properties:
type:
type: string
enum: [luna-ws-notification]
description: Event will be sent with `suit` type.
required: [type]
callback_basic_authorization:
type: object
properties:
type:
type: string
description: Authorization type.
enum: [basic]
login:
type: string
maxLength: 128
description: Login.
password:
type: string
maxLength: 128
description: Password.
required: [ type, login, password]
description: Callback basic authorization parameters.
callback_http:
allOf:
- $ref: "#/components/schemas/callback_base"
- type: object
properties:
type:
type: string
enum: [http]
description: Event will be sent to http url.
url:
type: string
description: Callback url.
authorization:
$ref: '#/components/schemas/callback_basic_authorization'
params:
type: object
properties:
timeout:
type: integer
default: 60
description: Callback request timeout.
content_type:
type: string
default: application/json
enum: [application/json]
description: Callback request content type.
headers:
type: object
description: Callback request headers.
additionalProperties: true
description: Callback request parameters
required: [url, type]
callback:
oneOf:
- $ref: '#/components/schemas/callback_http'
- $ref: '#/components/schemas/callback_ws'
discriminator:
propertyName: type
mapping:
http: '#/components/schemas/callback_http'
luna-ws-notification: '#/components/schemas/callback_ws'
image_retain_policy:
type: object
description: |
Image retain policy applicable when analytic `overview` target is specified,
and configures parameters with which will image be saved.
properties:
mimetype:
type: string
enum:
- PNG
- JPEG
default: JPEG
description: Image format.
quality:
allOf:
- $ref: '#/components/schemas/number01'
- default: 1
description: Image quality, on a scale from 0 (worst) to 1 (best). Has no effect on `PNG`.
max_size:
type: integer
minimum: 0
default: 640
description: Image max size, in pxl. Neither the width nor the height will exceed this value.
period_event_policy:
type: object
properties:
trigger:
type: string
enum: [period]
interval:
type: float
description: Event generation period interval.
minimum: 0.0
default: 1.0
required: [trigger]
start_event_policy:
type: object
properties:
trigger:
type: string
enum: [start]
required: [trigger]
end_event_policy:
type: object
properties:
trigger:
type: string
enum: [end]
required: [trigger]
parameters:
type: object
description: |
Analytic parameters for stream processing.
There are default analytic parameters which will be applied for stream processing if no one specified here.
properties:
targets:
type: array
default: [suit]
items:
type: string
enum: [suit]
description: |
Estimations to perform on the video.
`suit` will calculate some `suit` value if specified.
`overview` will add image to events.
callbacks:
type: array
description: |
Callbacks parameters.
`http` type callback sends events to the specified url by POST request.
`luna-ws-notification` type callback sends events to websocket using sender.
maxItems: 10
items:
$ref: '#/components/schemas/callback'
parameters:
type: object
description: Estimation parameters.
properties:
event_policy:
description: |
Event policy.
When suit appears on frame, the new `track` will starts, when suit disappears, `track` will stops.
- when track starts (`trigger` - `start`)
- when track ends (`trigger` - `end`)
- periodically while track exists (`trigger` - `period`)
type: object
properties:
oneOf:
- $ref: '#/components/schemas/period_event_policy'
- $ref: '#/components/schemas/start_event_policy'
- $ref: '#/components/schemas/end_event_policy'
discriminator:
propertyName: trigger
mapping:
period: '#/components/schemas/period_event_policy'
start: '#/components/schemas/start_event_policy'
end: '#/components/schemas/end_event_policy'
default:
trigger: start
image_retain_policy:
$ref: '#/components/schemas/image_retain_policy'
probe_count:
description: |
A number of consecutive suit estimations with not 'none' class. This parameter is intended to
prevent false positives from analytics.
type: integer
minimum: 0
default: 3
roi:
$ref: '#/components/schemas/roi'
rate:
allOf:
- $ref: '#/components/schemas/rate'
- default:
unit: second
period: 1
stream:
type: object
description: |
Full stream creation schema is available in `Luna-Video-Manager` documentation.
The presented schema described only analytics parameters.
properties:
data:
type: object
description: stream data
additionalProperties: true
analytics:
type: array
description: analytics list
items:
type: object
description: analytics
properties:
analytic_name:
type: string
maxLength: 36
pattern: '^[a-zA-Z0-9_\-]{1,36}$'
description: Analytic name.
enum: [suit_analytics]
parameters:
type: object
$ref: '#/components/schemas/parameters'
required: [analytic_name]
additionalProperties: true
required: [data, analytics]
string36_nullable:
type: string
maxLength: 36
nullable: true
longitude:
type: number
minimum: -180
maximum: 180
example: 36.616
latitude:
type: number
minimum: -90
maximum: 90
example: 55.752
geo_position:
type: object
nullable: true
description: Geo position specified by geographic coordinates - longitude and latitude.
properties:
longitude:
allOf:
- $ref: '#/components/schemas/longitude'
- description: Longitude in degrees.
latitude:
allOf:
- $ref: '#/components/schemas/latitude'
- description: Latitude in degrees.
required: [longitude, latitude]
example:
longitude: 36.616
latitude: 55.752
uuid:
type: string
format: uuid
pattern: '^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$'
example: "557d54ec-29ad-4f3c-93b4-c9092ef12515"
video_segment:
type: object
properties:
start_time_offset:
type: number
example: 0.123
description: Start video segment offset(seconds).
end_time_offset:
type: number
example: 1.234
description: Eng video segment offset(seconds).
required: [ start_time_offset, end_time_offset ]
callback_request:
type: object
properties:
event_type:
type: string
enum: [suit]
description: Event type.
account_id:
allOf:
- $ref: '#/components/schemas/uuid'
description: Account id of event.
event_create_time:
type: string
description: Event creation time.
event_end_time:
type: string
description: Event end time.
event:
type: object
description: Event.
properties:
stream_id:
allOf:
- $ref: '#/components/schemas/uuid'
description: Stream ID.
event_id:
allOf:
- $ref: '#/components/schemas/uuid'
description: Event ID.
track_id:
allOf:
- $ref: '#/components/schemas/uuid'
description: Track id.
nullable: true
video_segment:
$ref: '#/components/schemas/video_segment'
overview:
type: object
properties:
time_offset:
type: number
description: time offset
image:
type: str
description: link to image
required: [time_offset]
location:
type: object
properties:
city:
allOf:
- $ref: '#/components/schemas/string36_nullable'
example: Moscow
description: City that stream belongs.
area:
allOf:
- $ref: '#/components/schemas/string36_nullable'
example: Central
description: Area that stream belongs.
district:
allOf:
- $ref: '#/components/schemas/string36_nullable'
example: Basmanny
description: District that stream belongs.
street:
allOf:
- $ref: '#/components/schemas/string36_nullable'
example: Podsosensky lane
description: Street that stream belongs.
house_number:
allOf:
- $ref: '#/components/schemas/string36_nullable'
example: 23 bldg.3
description: Street that stream belongs.
geo_position:
$ref: '#/components/schemas/geo_position'
description: |
Stream location parameters.
Required callback `location` target to send this value.
required: [stream_id, event_id, suit_class, track_id]
required: [event_type, event, account_id, event_create_time, event_end_time]
paths:
/suit_analytics:
post:
tags:
- suit analytics
summary: stream creation
description: |
Stream creation superficial request with detailed analytics parameters description.
Full request description (exclude for this analytics description) available at `Luna-Video-Manager` documentation.
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/stream'
example:
data:
reference: rtsp://stream_url
type: stream
downloadable: false
analytics:
- analytic_name: suit_analytics
parameters:
parameters:
probe_count: 2
image_retain_policy:
mimetype: PNG
roi:
mode: abs
x: 0
y: 0
width: 1000
height: 500
callbacks:
- url: "http://127.0.0.1:8001"
type: http
- url: "http://127.0.0.1:8002"
type: http
- type: luna-ws-notification
targets:
- suit
- overview
callbacks:
onData:
'{$request.body.callbacks.*.url}':
post:
requestBody:
description: subscription payload
content:
application/json:
schema:
$ref: '#/components/schemas/callback_request'
The *spec.html* file contains openapi documentation with analytics parameters description in html format.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8" />
<title>Suit Analytics</title>
<!-- needed for adaptive design -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
padding: 0;
margin: 0;
}
</style>
<script src="https://cdn.redoc.ly/redoc/v2.0.0/bundles/redoc.standalone.js"></script><style data-styled="true" data-styled-version="5.3.3">.fcjMZs{width:calc(100% - 40%);padding:0 40px;}/*!sc*/
@media print,screen and (max-width:75rem){.fcjMZs{width:100%;padding:40px 40px;}}/*!sc*/
data-styled.g4[id="sc-hKwDye"]{content:"fcjMZs,"}/*!sc*/
.kKqedw{padding:40px 0;}/*!sc*/
.kKqedw:last-child{min-height:calc(100vh + 1px);}/*!sc*/
.sc-eCImPb > .sc-eCImPb:last-child{min-height:initial;}/*!sc*/
@media print,screen and (max-width:75rem){.kKqedw{padding:0;}}/*!sc*/
.hexvPn{padding:40px 0;position:relative;}/*!sc*/
.hexvPn:last-child{min-height:calc(100vh + 1px);}/*!sc*/
.sc-eCImPb > .sc-eCImPb:last-child{min-height:initial;}/*!sc*/
@media print,screen and (max-width:75rem){.hexvPn{padding:0;}}/*!sc*/
.hexvPn:not(:last-of-type):after{position:absolute;bottom:0;width:100%;display:block;content:'';border-bottom:1px solid rgba(0,0,0,0.2);}/*!sc*/
data-styled.g5[id="sc-eCImPb"]{content:"kKqedw,hexvPn,"}/*!sc*/
.vSqXI{width:40%;color:#ffffff;background-color:#263238;padding:0 40px;}/*!sc*/
@media print,screen and (max-width:75rem){.vSqXI{width:100%;padding:40px 40px;}}/*!sc*/
data-styled.g6[id="sc-jRQBWg"]{content:"vSqXI,"}/*!sc*/
.dKDnzb{background-color:#263238;}/*!sc*/
data-styled.g7[id="sc-gKclnd"]{content:"dKDnzb,"}/*!sc*/
.jTXZbd{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;padding:0;}/*!sc*/
@media print,screen and (max-width:75rem){.jTXZbd{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;}}/*!sc*/
data-styled.g8[id="sc-iCfMLu"]{content:"jTXZbd,"}/*!sc*/
.kwcwWd{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#333333;}/*!sc*/
data-styled.g9[id="sc-furwcr"]{content:"kwcwWd,"}/*!sc*/
.QwiBS{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;margin:0 0 20px;}/*!sc*/
data-styled.g10[id="sc-pVTFL"]{content:"QwiBS,"}/*!sc*/
.hwMGJl{color:#ffffff;}/*!sc*/
data-styled.g12[id="sc-kDTinF"]{content:"hwMGJl,"}/*!sc*/
.kyZwVG{border-bottom:1px solid rgba(38,50,56,0.3);margin:1em 0 1em 0;color:rgba(38,50,56,0.5);font-weight:normal;text-transform:uppercase;font-size:0.929em;line-height:20px;}/*!sc*/
data-styled.g13[id="sc-iqseJM"]{content:"kyZwVG,"}/*!sc*/
.bNUQy{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;outline:0;}/*!sc*/
.bNUQy:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;}/*!sc*/
h1:hover > .sc-crHmcD::before,h2:hover > .bNUQy::before,.bNUQy:hover::before{visibility:visible;}/*!sc*/
data-styled.g14[id="sc-crHmcD"]{content:"bNUQy,"}/*!sc*/
.jLPfXH{height:18px;width:18px;min-width:18px;vertical-align:middle;float:right;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);}/*!sc*/
.cUKGZj{height:18px;width:18px;min-width:18px;vertical-align:middle;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);}/*!sc*/
.gHElpb{height:1.5em;width:1.5em;min-width:1.5em;vertical-align:middle;float:left;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);}/*!sc*/
.laPRNF{height:20px;width:20px;min-width:20px;vertical-align:middle;float:right;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(0);-ms-transform:rotateZ(0);transform:rotateZ(0);}/*!sc*/
.laPRNF polygon{fill:white;}/*!sc*/
data-styled.g15[id="sc-egiyK"]{content:"jLPfXH,cUKGZj,gHElpb,laPRNF,"}/*!sc*/
.kCCtqV{border-left:1px solid #7c7cbb;box-sizing:border-box;position:relative;padding:10px 10px 10px 0;}/*!sc*/
@media screen and (max-width:50rem){.kCCtqV{display:block;overflow:hidden;}}/*!sc*/
tr:first-of-type > .sc-hBUSln,tr.last > .kCCtqV{border-left-width:0;background-position:top left;background-repeat:no-repeat;background-size:1px 100%;}/*!sc*/
tr:first-of-type > .sc-hBUSln{background-image:linear-gradient( to bottom, transparent 0%, transparent 22px, #7c7cbb 22px, #7c7cbb 100% );}/*!sc*/
tr.last > .sc-hBUSln{background-image:linear-gradient( to bottom, #7c7cbb 0%, #7c7cbb 22px, transparent 22px, transparent 100% );}/*!sc*/
tr.last + tr > .sc-hBUSln{border-left-color:transparent;}/*!sc*/
tr.last:first-child > .sc-hBUSln{background:none;border-left-color:transparent;}/*!sc*/
data-styled.g18[id="sc-hBUSln"]{content:"kCCtqV,"}/*!sc*/
.fwnkRw{vertical-align:top;line-height:20px;white-space:nowrap;font-size:13px;font-family:Courier,monospace;}/*!sc*/
.fwnkRw.deprecated{-webkit-text-decoration:line-through;text-decoration:line-through;color:#707070;}/*!sc*/
data-styled.g20[id="sc-fFeiMQ"]{content:"fwnkRw,"}/*!sc*/
.fOwWgB{border-bottom:1px solid #9fb4be;padding:10px 0;width:75%;box-sizing:border-box;}/*!sc*/
tr.expanded .sc-bkkeKt{border-bottom:none;}/*!sc*/
@media screen and (max-width:50rem){.fOwWgB{padding:0 20px;border-bottom:none;border-left:1px solid #7c7cbb;}tr.last > .sc-bkkeKt{border-left:none;}}/*!sc*/
data-styled.g21[id="sc-bkkeKt"]{content:"fOwWgB,"}/*!sc*/
.fMdASL{color:#7c7cbb;font-family:Courier,monospace;margin-right:10px;}/*!sc*/
.fMdASL::before{content:'';display:inline-block;vertical-align:middle;width:10px;height:1px;background:#7c7cbb;}/*!sc*/
.fMdASL::after{content:'';display:inline-block;vertical-align:middle;width:1px;background:#7c7cbb;height:7px;}/*!sc*/
data-styled.g22[id="sc-ieecCq"]{content:"fMdASL,"}/*!sc*/
.wbnF{border-collapse:separate;border-radius:3px;font-size:14px;border-spacing:0;width:100%;}/*!sc*/
.wbnF > tr{vertical-align:middle;}/*!sc*/
@media screen and (max-width:50rem){.wbnF{display:block;}.wbnF > tr,.wbnF > tbody > tr{display:block;}}/*!sc*/
@media screen and (max-width:50rem) and (-ms-high-contrast:none){.wbnF td{float:left;width:100%;}}/*!sc*/
.wbnF .sc-dJjYzT,.wbnF .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT,.wbnF .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT{margin:1em;margin-right:0;background:#fafafa;}/*!sc*/
.wbnF .sc-dJjYzT .sc-dJjYzT,.wbnF .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT,.wbnF .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT{background:#ffffff;}/*!sc*/
data-styled.g24[id="sc-hGPBjI"]{content:"wbnF,"}/*!sc*/
.eCLDJY > ul{list-style:none;padding:0;margin:0;margin:0 -5px;}/*!sc*/
.eCLDJY > ul > li{padding:5px 10px;display:inline-block;background-color:#11171a;border-bottom:1px solid rgba(0,0,0,0.5);cursor:pointer;text-align:center;outline:none;color:#ccc;margin:0 5px 5px 5px;border:1px solid #07090b;border-radius:5px;min-width:60px;font-size:0.9em;font-weight:bold;}/*!sc*/
.eCLDJY > ul > li.react-tabs__tab--selected{color:#333333;background:#ffffff;}/*!sc*/
.eCLDJY > ul > li.react-tabs__tab--selected:focus{outline:auto;}/*!sc*/
.eCLDJY > ul > li:only-child{-webkit-flex:none;-ms-flex:none;flex:none;min-width:100px;}/*!sc*/
.eCLDJY > ul > li.tab-success{color:#1d8127;}/*!sc*/
.eCLDJY > ul > li.tab-redirect{color:#ffa500;}/*!sc*/
.eCLDJY > ul > li.tab-info{color:#87ceeb;}/*!sc*/
.eCLDJY > ul > li.tab-error{color:#d41f1c;}/*!sc*/
.eCLDJY > .react-tabs__tab-panel{background:#11171a;}/*!sc*/
.eCLDJY > .react-tabs__tab-panel > div,.eCLDJY > .react-tabs__tab-panel > pre{padding:20px;margin:0;}/*!sc*/
.eCLDJY > .react-tabs__tab-panel > div > pre{padding:0;}/*!sc*/
data-styled.g30[id="sc-cxpSdN"]{content:"eCLDJY,"}/*!sc*/
.hGZPxu code[class*='language-'],.hGZPxu pre[class*='language-']{text-shadow:0 -0.1em 0.2em black;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none;}/*!sc*/
@media print{.hGZPxu code[class*='language-'],.hGZPxu pre[class*='language-']{text-shadow:none;}}/*!sc*/
.hGZPxu pre[class*='language-']{padding:1em;margin:0.5em 0;overflow:auto;}/*!sc*/
.hGZPxu .token.comment,.hGZPxu .token.prolog,.hGZPxu .token.doctype,.hGZPxu .token.cdata{color:hsl(30,20%,50%);}/*!sc*/
.hGZPxu .token.punctuation{opacity:0.7;}/*!sc*/
.hGZPxu .namespace{opacity:0.7;}/*!sc*/
.hGZPxu .token.property,.hGZPxu .token.tag,.hGZPxu .token.number,.hGZPxu .token.constant,.hGZPxu .token.symbol{color:#4a8bb3;}/*!sc*/
.hGZPxu .token.boolean{color:#e64441;}/*!sc*/
.hGZPxu .token.selector,.hGZPxu .token.attr-name,.hGZPxu .token.string,.hGZPxu .token.char,.hGZPxu .token.builtin,.hGZPxu .token.inserted{color:#a0fbaa;}/*!sc*/
.hGZPxu .token.selector + a,.hGZPxu .token.attr-name + a,.hGZPxu .token.string + a,.hGZPxu .token.char + a,.hGZPxu .token.builtin + a,.hGZPxu .token.inserted + a,.hGZPxu .token.selector + a:visited,.hGZPxu .token.attr-name + a:visited,.hGZPxu .token.string + a:visited,.hGZPxu .token.char + a:visited,.hGZPxu .token.builtin + a:visited,.hGZPxu .token.inserted + a:visited{color:#4ed2ba;-webkit-text-decoration:underline;text-decoration:underline;}/*!sc*/
.hGZPxu .token.property.string{color:white;}/*!sc*/
.hGZPxu .token.operator,.hGZPxu .token.entity,.hGZPxu .token.url,.hGZPxu .token.variable{color:hsl(40,90%,60%);}/*!sc*/
.hGZPxu .token.atrule,.hGZPxu .token.attr-value,.hGZPxu .token.keyword{color:hsl(350,40%,70%);}/*!sc*/
.hGZPxu .token.regex,.hGZPxu .token.important{color:#e90;}/*!sc*/
.hGZPxu .token.important,.hGZPxu .token.bold{font-weight:bold;}/*!sc*/
.hGZPxu .token.italic{font-style:italic;}/*!sc*/
.hGZPxu .token.entity{cursor:help;}/*!sc*/
.hGZPxu .token.deleted{color:red;}/*!sc*/
data-styled.g32[id="sc-iJKOTD"]{content:"hGZPxu,"}/*!sc*/
.gjxyHD{opacity:0.7;-webkit-transition:opacity 0.3s ease;transition:opacity 0.3s ease;text-align:right;}/*!sc*/
.gjxyHD:focus-within{opacity:1;}/*!sc*/
.gjxyHD > button{background-color:transparent;border:0;color:inherit;padding:2px 10px;font-family:Roboto,sans-serif;font-size:14px;line-height:1.5em;cursor:pointer;outline:0;}/*!sc*/
.gjxyHD > button:hover,.gjxyHD > button:focus{background:rgba(255,255,255,0.1);}/*!sc*/
data-styled.g33[id="sc-giYglK"]{content:"gjxyHD,"}/*!sc*/
.eqZXtE{position:relative;}/*!sc*/
data-styled.g38[id="sc-ikJyIC"]{content:"eqZXtE,"}/*!sc*/
.hgRRjA{margin-left:10px;text-transform:none;font-size:0.929em;color:black;}/*!sc*/
data-styled.g42[id="sc-cCcXHH"]{content:"hgRRjA,"}/*!sc*/
.fYaHea{font-family:Roboto,sans-serif;font-weight:400;line-height:1.5em;}/*!sc*/
.fYaHea p:last-child{margin-bottom:0;}/*!sc*/
.fYaHea h1{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#32329f;margin-top:0;}/*!sc*/
.fYaHea h2{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;}/*!sc*/
.fYaHea code{color:#e53935;background-color:rgba(38,50,56,0.05);font-family:Courier,monospace;border-radius:2px;border:1px solid rgba(38,50,56,0.1);padding:0 5px;font-size:13px;font-weight:400;word-break:break-word;}/*!sc*/
.fYaHea pre{font-family:Courier,monospace;white-space:pre;background-color:#11171a;color:white;padding:20px;overflow-x:auto;line-height:normal;border-radius:0px;border:1px solid rgba(38,50,56,0.1);}/*!sc*/
.fYaHea pre code{background-color:transparent;color:white;padding:0;}/*!sc*/
.fYaHea pre code:before,.fYaHea pre code:after{content:none;}/*!sc*/
.fYaHea blockquote{margin:0;margin-bottom:1em;padding:0 15px;color:#777;border-left:4px solid #ddd;}/*!sc*/
.fYaHea img{max-width:100%;box-sizing:content-box;}/*!sc*/
.fYaHea ul,.fYaHea ol{padding-left:2em;margin:0;margin-bottom:1em;}/*!sc*/
.fYaHea ul ul,.fYaHea ol ul,.fYaHea ul ol,.fYaHea ol ol{margin-bottom:0;margin-top:0;}/*!sc*/
.fYaHea table{display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all;border-collapse:collapse;border-spacing:0;margin-top:1.5em;margin-bottom:1.5em;}/*!sc*/
.fYaHea table tr{background-color:#fff;border-top:1px solid #ccc;}/*!sc*/
.fYaHea table tr:nth-child(2n){background-color:#fafafa;}/*!sc*/
.fYaHea table th,.fYaHea table td{padding:6px 13px;border:1px solid #ddd;}/*!sc*/
.fYaHea table th{text-align:left;font-weight:bold;}/*!sc*/
.fYaHea .share-link{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;outline:0;}/*!sc*/
.fYaHea .share-link:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;}/*!sc*/
.fYaHea h1:hover > .share-link::before,.fYaHea h2:hover > .share-link::before,.fYaHea .share-link:hover::before{visibility:visible;}/*!sc*/
.fYaHea a{-webkit-text-decoration:auto;text-decoration:auto;color:#32329f;}/*!sc*/
.fYaHea a:visited{color:#32329f;}/*!sc*/
.fYaHea a:hover{color:#6868cf;-webkit-text-decoration:auto;text-decoration:auto;}/*!sc*/
.fhPxtY{font-family:Roboto,sans-serif;font-weight:400;line-height:1.5em;}/*!sc*/
.fhPxtY p:last-child{margin-bottom:0;}/*!sc*/
.fhPxtY p:first-child{margin-top:0;}/*!sc*/
.fhPxtY p:last-child{margin-bottom:0;}/*!sc*/
.fhPxtY h1{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#32329f;margin-top:0;}/*!sc*/
.fhPxtY h2{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;}/*!sc*/
.fhPxtY code{color:#e53935;background-color:rgba(38,50,56,0.05);font-family:Courier,monospace;border-radius:2px;border:1px solid rgba(38,50,56,0.1);padding:0 5px;font-size:13px;font-weight:400;word-break:break-word;}/*!sc*/
.fhPxtY pre{font-family:Courier,monospace;white-space:pre;background-color:#11171a;color:white;padding:20px;overflow-x:auto;line-height:normal;border-radius:0px;border:1px solid rgba(38,50,56,0.1);}/*!sc*/
.fhPxtY pre code{background-color:transparent;color:white;padding:0;}/*!sc*/
.fhPxtY pre code:before,.fhPxtY pre code:after{content:none;}/*!sc*/
.fhPxtY blockquote{margin:0;margin-bottom:1em;padding:0 15px;color:#777;border-left:4px solid #ddd;}/*!sc*/
.fhPxtY img{max-width:100%;box-sizing:content-box;}/*!sc*/
.fhPxtY ul,.fhPxtY ol{padding-left:2em;margin:0;margin-bottom:1em;}/*!sc*/
.fhPxtY ul ul,.fhPxtY ol ul,.fhPxtY ul ol,.fhPxtY ol ol{margin-bottom:0;margin-top:0;}/*!sc*/
.fhPxtY table{display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all;border-collapse:collapse;border-spacing:0;margin-top:1.5em;margin-bottom:1.5em;}/*!sc*/
.fhPxtY table tr{background-color:#fff;border-top:1px solid #ccc;}/*!sc*/
.fhPxtY table tr:nth-child(2n){background-color:#fafafa;}/*!sc*/
.fhPxtY table th,.fhPxtY table td{padding:6px 13px;border:1px solid #ddd;}/*!sc*/
.fhPxtY table th{text-align:left;font-weight:bold;}/*!sc*/
.fhPxtY .share-link{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;outline:0;}/*!sc*/
.fhPxtY .share-link:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;}/*!sc*/
.fhPxtY h1:hover > .share-link::before,.fhPxtY h2:hover > .share-link::before,.fhPxtY .share-link:hover::before{visibility:visible;}/*!sc*/
.fhPxtY a{-webkit-text-decoration:auto;text-decoration:auto;color:#32329f;}/*!sc*/
.fhPxtY a:visited{color:#32329f;}/*!sc*/
.fhPxtY a:hover{color:#6868cf;-webkit-text-decoration:auto;text-decoration:auto;}/*!sc*/
data-styled.g43[id="sc-cidDSM"]{content:"fYaHea,fhPxtY,"}/*!sc*/
.ddxnzs{position:relative;}/*!sc*/
data-styled.g45[id="sc-caiLqq"]{content:"ddxnzs,"}/*!sc*/
.jOBPNj:hover > .sc-giYglK{opacity:1;}/*!sc*/
data-styled.g50[id="sc-jObWnj"]{content:"jOBPNj,"}/*!sc*/
.sRoPx{font-family:Courier,monospace;font-size:13px;white-space:pre;contain:content;overflow-x:auto;}/*!sc*/
.sRoPx .redoc-json code > .collapser{display:none;pointer-events:none;}/*!sc*/
.sRoPx .callback-function{color:gray;}/*!sc*/
.sRoPx .collapser:after{content:'-';cursor:pointer;}/*!sc*/
.sRoPx .collapsed > .collapser:after{content:'+';cursor:pointer;}/*!sc*/
.sRoPx .ellipsis:after{content:' … ';}/*!sc*/
.sRoPx .collapsible{margin-left:2em;}/*!sc*/
.sRoPx .hoverable{padding-top:1px;padding-bottom:1px;padding-left:2px;padding-right:2px;border-radius:2px;}/*!sc*/
.sRoPx .hovered{background-color:rgba(235,238,249,1);}/*!sc*/
.sRoPx .collapser{background-color:transparent;border:0;color:#fff;font-family:Courier,monospace;font-size:13px;padding-right:6px;padding-left:6px;padding-top:0;padding-bottom:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;width:15px;height:15px;position:absolute;top:4px;left:-1.5em;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-user-select:none;padding:2px;}/*!sc*/
.sRoPx .collapser:focus{outline-color:#fff;outline-style:dotted;outline-width:1px;}/*!sc*/
.sRoPx ul{list-style-type:none;padding:0px;margin:0px 0px 0px 26px;}/*!sc*/
.sRoPx li{position:relative;display:block;}/*!sc*/
.sRoPx .hoverable{display:inline-block;}/*!sc*/
.sRoPx .selected{outline-style:solid;outline-width:1px;outline-style:dotted;}/*!sc*/
.sRoPx .collapsed > .collapsible{display:none;}/*!sc*/
.sRoPx .ellipsis{display:none;}/*!sc*/
.sRoPx .collapsed > .ellipsis{display:inherit;}/*!sc*/
data-styled.g51[id="sc-dPiLbb"]{content:"sRoPx,"}/*!sc*/
.eidhyg{padding:0.9em;background-color:rgba(38,50,56,0.4);margin:0 0 10px 0;display:block;font-family:Montserrat,sans-serif;font-size:0.929em;line-height:1.5em;}/*!sc*/
data-styled.g52[id="sc-bBHHxi"]{content:"eidhyg,"}/*!sc*/
.iYcnri{font-family:Montserrat,sans-serif;font-size:12px;position:absolute;z-index:1;top:-11px;left:12px;font-weight:600;color:rgba(255,255,255,0.7);}/*!sc*/
data-styled.g53[id="sc-cNKqjZ"]{content:"iYcnri,"}/*!sc*/
.bcpfGN{position:relative;}/*!sc*/
data-styled.g54[id="sc-AjmGg"]{content:"bcpfGN,"}/*!sc*/
.iUheaL{margin-top:15px;}/*!sc*/
data-styled.g57[id="sc-jgrJph"]{content:"iUheaL,"}/*!sc*/
.iFIEVq button{background-color:transparent;border:0;outline:0;font-size:13px;font-family:Courier,monospace;cursor:pointer;padding:0;color:#333333;}/*!sc*/
.iFIEVq button:focus{font-weight:600;}/*!sc*/
.iFIEVq .sc-egiyK{height:1.1em;width:1.1em;}/*!sc*/
.iFIEVq .sc-egiyK polygon{fill:#666;}/*!sc*/
data-styled.g58[id="sc-gSQFLo"]{content:"iFIEVq,"}/*!sc*/
.gHbDui{vertical-align:middle;font-size:13px;line-height:20px;}/*!sc*/
data-styled.g59[id="sc-lbhJGD"]{content:"gHbDui,"}/*!sc*/
.eLMLxg{color:rgba(102,102,102,0.9);}/*!sc*/
data-styled.g60[id="sc-iNGGcK"]{content:"eLMLxg,"}/*!sc*/
.ifsMbX{color:#666;}/*!sc*/
data-styled.g61[id="sc-jeraig"]{content:"ifsMbX,"}/*!sc*/
.eyZzuM{vertical-align:middle;font-size:13px;line-height:20px;}/*!sc*/
data-styled.g63[id="sc-nVkyK"]{content:"eyZzuM,"}/*!sc*/
.eSyGZd{color:#d41f1c;font-size:0.9em;font-weight:normal;margin-left:20px;line-height:1;}/*!sc*/
data-styled.g64[id="sc-hiwPVj"]{content:"eSyGZd,"}/*!sc*/
.JLmOC{color:#6868cf;}/*!sc*/
data-styled.g65[id="sc-ehCJOs"]{content:"JLmOC,"}/*!sc*/
.dezuum{margin-top:0;margin-bottom:0.5em;}/*!sc*/
data-styled.g93[id="sc-dFtzxp"]{content:"dezuum,"}/*!sc*/
.cfBNBf{border:1px solid #32329f;color:#32329f;font-weight:normal;margin-left:0.5em;padding:4px 8px 4px;display:inline-block;-webkit-text-decoration:none;text-decoration:none;cursor:pointer;}/*!sc*/
data-styled.g94[id="sc-brSvTw"]{content:"cfBNBf,"}/*!sc*/
.eXEyiA{width:9ex;display:inline-block;height:13px;line-height:13px;background-color:#333;border-radius:3px;background-repeat:no-repeat;background-position:6px 4px;font-size:7px;font-family:Verdana,sans-serif;color:white;text-transform:uppercase;text-align:center;font-weight:bold;vertical-align:middle;margin-right:6px;margin-top:2px;}/*!sc*/
.eXEyiA.get{background-color:#2F8132;}/*!sc*/
.eXEyiA.post{background-color:#186FAF;}/*!sc*/
.eXEyiA.put{background-color:#95507c;}/*!sc*/
.eXEyiA.options{background-color:#947014;}/*!sc*/
.eXEyiA.patch{background-color:#bf581d;}/*!sc*/
.eXEyiA.delete{background-color:#cc3333;}/*!sc*/
.eXEyiA.basic{background-color:#707070;}/*!sc*/
.eXEyiA.link{background-color:#07818F;}/*!sc*/
.eXEyiA.head{background-color:#A23DAD;}/*!sc*/
.eXEyiA.hook{background-color:#32329f;}/*!sc*/
data-styled.g101[id="sc-ilfuhL"]{content:"eXEyiA,"}/*!sc*/
.cBjpjq{margin:0;padding:0;}/*!sc*/
.cBjpjq:first-child{padding-bottom:32px;}/*!sc*/
.sc-uojGG .sc-uojGG{font-size:0.929em;}/*!sc*/
.fZvBef{margin:0;padding:0;display:none;}/*!sc*/
.fZvBef:first-child{padding-bottom:32px;}/*!sc*/
.sc-uojGG .sc-uojGG{font-size:0.929em;}/*!sc*/
data-styled.g102[id="sc-uojGG"]{content:"cBjpjq,fZvBef,"}/*!sc*/
.iVJVQY{list-style:none inside none;overflow:hidden;text-overflow:ellipsis;padding:0;}/*!sc*/
data-styled.g103[id="sc-bilyIR"]{content:"iVJVQY,"}/*!sc*/
.cbGskd{cursor:pointer;color:#333333;margin:0;padding:12.5px 20px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;font-family:Montserrat,sans-serif;font-size:0.929em;text-transform:none;background-color:#fafafa;}/*!sc*/
.cbGskd:hover{color:#32329f;background-color:#e1e1e1;}/*!sc*/
.cbGskd .sc-egiyK{height:1.5em;width:1.5em;}/*!sc*/
.cbGskd .sc-egiyK polygon{fill:#333333;}/*!sc*/
.jDuioL{cursor:pointer;color:#333333;margin:0;padding:12.5px 20px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;font-family:Montserrat,sans-serif;background-color:#fafafa;}/*!sc*/
.jDuioL:hover{color:#32329f;background-color:#ededed;}/*!sc*/
.jDuioL .sc-egiyK{height:1.5em;width:1.5em;}/*!sc*/
.jDuioL .sc-egiyK polygon{fill:#333333;}/*!sc*/
data-styled.g104[id="sc-eGPXGI"]{content:"cbGskd,jDuioL,"}/*!sc*/
.hTIqli{display:inline-block;vertical-align:middle;width:auto;overflow:hidden;text-overflow:ellipsis;}/*!sc*/
.gPtqKT{display:inline-block;vertical-align:middle;width:calc(100% - 38px);overflow:hidden;text-overflow:ellipsis;}/*!sc*/
data-styled.g105[id="sc-hAcGzb"]{content:"hTIqli,gPtqKT,"}/*!sc*/
.hjTfvW{font-size:0.8em;margin-top:10px;text-align:center;position:fixed;width:260px;bottom:0;background:#fafafa;}/*!sc*/
.hjTfvW a,.hjTfvW a:visited,.hjTfvW a:hover{color:#333333 !important;padding:5px 0;border-top:1px solid #e1e1e1;-webkit-text-decoration:none;text-decoration:none;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;}/*!sc*/
.hjTfvW img{width:15px;margin-right:5px;}/*!sc*/
@media screen and (max-width:50rem){.hjTfvW{width:100%;}}/*!sc*/
data-styled.g106[id="sc-kYHfwS"]{content:"hjTfvW,"}/*!sc*/
.hrnNnQ{border:0;width:100%;text-align:left;}/*!sc*/
.hrnNnQ > *{vertical-align:middle;}/*!sc*/
.hrnNnQ .sc-egiyK polygon{fill:#c2c2c2;}/*!sc*/
data-styled.g107[id="sc-xiLah"]{content:"hrnNnQ,"}/*!sc*/
.ixwUsq{-webkit-text-decoration:none;text-decoration:none;margin-right:8px;}/*!sc*/
data-styled.g108[id="sc-dVNjXY"]{content:"ixwUsq,"}/*!sc*/
.iMeny{margin:0 5px 0 0;}/*!sc*/
data-styled.g109[id="sc-jHkVzv"]{content:"iMeny,"}/*!sc*/
.fuWZWu{padding:10px;border-radius:2px;margin-bottom:4px;line-height:1.5em;background-color:#F5F5F5;cursor:pointer;outline-color:#c2c2c2;}/*!sc*/
data-styled.g110[id="sc-bQtKYq"]{content:"fuWZWu,"}/*!sc*/
.jYAhkx{cursor:pointer;position:relative;margin-bottom:5px;}/*!sc*/
data-styled.g112[id="sc-FNXRL"]{content:"jYAhkx,"}/*!sc*/
.hxZHzL{font-family:Courier,monospace;margin-left:10px;-webkit-flex:1;-ms-flex:1;flex:1;overflow-x:hidden;text-overflow:ellipsis;}/*!sc*/
data-styled.g113[id="sc-jWUzzU"]{content:"hxZHzL,"}/*!sc*/
.bxamQ{outline:0;color:inherit;width:100%;text-align:left;cursor:pointer;padding:10px 30px 10px 20px;border-radius:4px 4px 0 0;background-color:#11171a;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;white-space:nowrap;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid transparent;border-bottom:0;-webkit-transition:border-color 0.25s ease;transition:border-color 0.25s ease;}/*!sc*/
.bxamQ ..sc-jWUzzU{color:#ffffff;}/*!sc*/
.bxamQ:focus{box-shadow:inset 0 2px 2px rgba(0,0,0,0.45),0 2px 0 rgba(128,128,128,0.25);}/*!sc*/
data-styled.g114[id="sc-eFegNN"]{content:"bxamQ,"}/*!sc*/
.huMmnt{font-size:0.929em;line-height:20px;background-color:#186FAF;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}/*!sc*/
data-styled.g115[id="sc-fmBCVi"]{content:"huMmnt,"}/*!sc*/
.fuqHqW{position:absolute;width:100%;z-index:100;background:#fafafa;color:#263238;box-sizing:border-box;box-shadow:0px 0px 6px rgba(0,0,0,0.33);overflow:hidden;border-bottom-left-radius:4px;border-bottom-right-radius:4px;-webkit-transition:all 0.25s ease;transition:all 0.25s ease;visibility:hidden;-webkit-transform:translateY(-50%) scaleY(0);-ms-transform:translateY(-50%) scaleY(0);transform:translateY(-50%) scaleY(0);}/*!sc*/
data-styled.g116[id="sc-lkgTHX"]{content:"fuqHqW,"}/*!sc*/
.fUpXCf{padding:10px;}/*!sc*/
data-styled.g117[id="sc-jlRLRk"]{content:"fUpXCf,"}/*!sc*/
.ggmziU{padding:5px;border:1px solid #ccc;background:#fff;word-break:break-all;color:#32329f;}/*!sc*/
.ggmziU > span{color:#333333;}/*!sc*/
data-styled.g118[id="sc-dUbtfd"]{content:"ggmziU,"}/*!sc*/
.bCWegr{font-size:1.3em;padding:0.2em 0;margin:3em 0 1.1em;color:#333333;font-weight:normal;}/*!sc*/
data-styled.g126[id="sc-gjNHFA"]{content:"bCWegr,"}/*!sc*/
.mgliX{margin-top:15px;}/*!sc*/
data-styled.g127[id="sc-fmciRz"]{content:"mgliX,"}/*!sc*/
.hNivrs{background:#11171a;padding:20px;}/*!sc*/
data-styled.g128[id="sc-eXlEPa"]{content:"hNivrs,"}/*!sc*/
.ilWCwE{margin-bottom:30px;}/*!sc*/
data-styled.g129[id="sc-iFMAIt"]{content:"ilWCwE,"}/*!sc*/
.lptroy{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:20px;height:20px;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;color:#32329f;}/*!sc*/
data-styled.g130[id="sc-iqVWFU"]{content:"lptroy,"}/*!sc*/
.iFeLMl{width:260px;background-color:#fafafa;overflow:hidden;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-backface-visibility:hidden;backface-visibility:hidden;height:100vh;position:-webkit-sticky;position:sticky;position:-webkit-sticky;top:0;}/*!sc*/
@media screen and (max-width:50rem){.iFeLMl{position:fixed;z-index:20;width:100%;background:#fafafa;display:none;}}/*!sc*/
@media print{.iFeLMl{display:none;}}/*!sc*/
data-styled.g131[id="sc-eWfVMQ"]{content:"iFeLMl,"}/*!sc*/
.cxHKpm{outline:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#f2f2f2;color:#32329f;display:none;cursor:pointer;position:fixed;right:20px;z-index:100;border-radius:50%;box-shadow:0 0 20px rgba(0,0,0,0.3);bottom:44px;width:60px;height:60px;padding:0 20px;}/*!sc*/
@media screen and (max-width:50rem){.cxHKpm{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}}/*!sc*/
.cxHKpm svg{color:#0065FB;}/*!sc*/
@media print{.cxHKpm{display:none;}}/*!sc*/
data-styled.g132[id="sc-kTLmzF"]{content:"cxHKpm,"}/*!sc*/
.cjkacg{font-family:Roboto,sans-serif;font-size:14px;font-weight:400;line-height:1.5em;color:#333333;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;position:relative;text-align:left;-webkit-font-smoothing:antialiased;font-smoothing:antialiased;text-rendering:optimizeSpeed !important;tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:100%;text-size-adjust:100%;}/*!sc*/
.cjkacg *{box-sizing:border-box;-webkit-tap-highlight-color:rgba(255,255,255,0);}/*!sc*/
data-styled.g133[id="sc-dwsnSq"]{content:"cjkacg,"}/*!sc*/
.jxeukX{z-index:1;position:relative;overflow:hidden;width:calc(100% - 260px);contain:layout;}/*!sc*/
@media print,screen and (max-width:50rem){.jxeukX{width:100%;}}/*!sc*/
data-styled.g134[id="sc-jtXEFf"]{content:"jxeukX,"}/*!sc*/
.bLydqy{background:#263238;position:absolute;top:0;bottom:0;right:0;width:calc((100% - 260px) * 0.4);}/*!sc*/
@media print,screen and (max-width:75rem){.bLydqy{display:none;}}/*!sc*/
data-styled.g135[id="sc-eldieg"]{content:"bLydqy,"}/*!sc*/
.qyDlP{padding:5px 0;}/*!sc*/
data-styled.g136[id="sc-kiIyQV"]{content:"qyDlP,"}/*!sc*/
.KrPzq{width:calc(100% - 40px);box-sizing:border-box;margin:0 20px;padding:5px 10px 5px 20px;border:0;border-bottom:1px solid #e1e1e1;font-family:Roboto,sans-serif;font-weight:bold;font-size:13px;color:#333333;background-color:transparent;outline:none;}/*!sc*/
data-styled.g137[id="sc-cLpAjG"]{content:"KrPzq,"}/*!sc*/
.liFvtQ{position:absolute;left:20px;height:1.8em;width:0.9em;}/*!sc*/
.liFvtQ path{fill:#333333;}/*!sc*/
data-styled.g138[id="sc-iIUQWv"]{content:"liFvtQ,"}/*!sc*/
</style>
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
</head>
<body>
<div id="redoc"><div class="sc-dwsnSq cjkacg redoc-wrap"><div class="sc-eWfVMQ iFeLMl menu-content" style="top:0px;height:calc(100vh - 0px)"><div role="search" class="sc-kiIyQV qyDlP"><svg class="sc-iIUQWv liFvtQ search-icon" version="1.1" viewBox="0 0 1000 1000" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><path d="M968.2,849.4L667.3,549c83.9-136.5,66.7-317.4-51.7-435.6C477.1-25,252.5-25,113.9,113.4c-138.5,138.3-138.5,362.6,0,501C219.2,730.1,413.2,743,547.6,666.5l301.9,301.4c43.6,43.6,76.9,14.9,104.2-12.4C981,928.3,1011.8,893,968.2,849.4z M524.5,522c-88.9,88.7-233,88.7-321.8,0c-88.9-88.7-88.9-232.6,0-321.3c88.9-88.7,233-88.7,321.8,0C613.4,289.4,613.4,433.3,524.5,522z"></path></svg><input type="text" value="" placeholder="Search..." aria-label="Search" class="sc-cLpAjG KrPzq search-input"/></div><div class="sc-ikJyIC eqZXtE scrollbar-container undefined"><ul class="sc-uojGG cBjpjq" role="menu"><li data-item-id="tag/suit-analytics" class="sc-bilyIR iVJVQY"><label type="tag" role="menuitem" class="sc-eGPXGI cbGskd -depth1"><span title="suit analytics" class="sc-hAcGzb hTIqli">suit analytics</span><svg class="sc-egiyK jLPfXH" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-uojGG fZvBef"><li data-item-id="tag/suit-analytics/paths/~1suit_analytics/post" class="sc-bilyIR iVJVQY"><label role="menuitem" class="sc-eGPXGI jDuioL -depth2"><span type="post" class="sc-ilfuhL eXEyiA operation-type post">post</span><span width="calc(100% - 38px)" class="sc-hAcGzb gPtqKT">stream creation</span></label></li></ul></li></ul><div class="sc-kYHfwS hjTfvW"><a target="_blank" rel="noopener noreferrer" href="https://redocly.com/redoc/">API docs by Redocly</a></div></div></div><div class="sc-kTLmzF cxHKpm"><div class="sc-iqVWFU lptroy"><svg class="" style="transform:translate(2px, -4px) rotate(180deg);transition:transform 0.2s ease" viewBox="0 0 926.23699 573.74994" version="1.1" x="0px" y="0px" width="15" height="15"><g transform="translate(904.92214,-879.1482)"><path d="
m -673.67664,1221.6502 -231.2455,-231.24803 55.6165,
-55.627 c 30.5891,-30.59485 56.1806,-55.627 56.8701,-55.627 0.6894,
0 79.8637,78.60862 175.9427,174.68583 l 174.6892,174.6858 174.6892,
-174.6858 c 96.079,-96.07721 175.253196,-174.68583 175.942696,
-174.68583 0.6895,0 26.281,25.03215 56.8701,
55.627 l 55.6165,55.627 -231.245496,231.24803 c -127.185,127.1864
-231.5279,231.248 -231.873,231.248 -0.3451,0 -104.688,
-104.0616 -231.873,-231.248 z
" fill="currentColor"></path></g></svg><svg class="" style="transform:translate(2px, 4px);transition:transform 0.2s ease" viewBox="0 0 926.23699 573.74994" version="1.1" x="0px" y="0px" width="15" height="15"><g transform="translate(904.92214,-879.1482)"><path d="
m -673.67664,1221.6502 -231.2455,-231.24803 55.6165,
-55.627 c 30.5891,-30.59485 56.1806,-55.627 56.8701,-55.627 0.6894,
0 79.8637,78.60862 175.9427,174.68583 l 174.6892,174.6858 174.6892,
-174.6858 c 96.079,-96.07721 175.253196,-174.68583 175.942696,
-174.68583 0.6895,0 26.281,25.03215 56.8701,
55.627 l 55.6165,55.627 -231.245496,231.24803 c -127.185,127.1864
-231.5279,231.248 -231.873,231.248 -0.3451,0 -104.688,
-104.0616 -231.873,-231.248 z
" fill="currentColor"></path></g></svg></div></div><div class="sc-jtXEFf jxeukX api-content"><div class="sc-eCImPb kKqedw"><div class="sc-iCfMLu jTXZbd"><div class="sc-hKwDye fcjMZs api-info"><h1 class="sc-furwcr sc-dFtzxp kwcwWd dezuum">Suit Analytics<!-- --> <span>(<!-- -->v.0.0.1<!-- -->)</span></h1><p>Download OpenAPI specification<!-- -->:<a download="openapi.json" target="_blank" class="sc-brSvTw cfBNBf">Download</a></p><div class="sc-iJKOTD sc-cidDSM hGZPxu fYaHea"></div><div class="sc-iJKOTD sc-cidDSM hGZPxu fYaHea" data-role="redoc-summary"></div><div class="sc-iJKOTD sc-cidDSM hGZPxu fYaHea" data-role="redoc-description"><p><code>Suit Analytics</code> is intended for determination of:</p>
<ul>
<li>suit_class parameter;</li>
</ul>
<p>The <code>suit</code> analytics decode stream frames taking into account <code>rate</code> parameter
from <a href="#tag/suit-analytics/paths/~1suit_analytics/post">stream creation request analytic parameters</a>
and performs the following actions:</p>
<ul>
<li>Calculates suit class</li>
<li>For each callback check it's conditions and execute callbacks, which fit conditions.</li>
</ul>
<p> The schema of data sending by <code>callback</code> described as stream creation request callback.</p>
</div></div></div></div><div id="tag/suit-analytics" data-section-id="tag/suit-analytics" class="sc-eCImPb kKqedw"><div class="sc-iCfMLu jTXZbd"><div class="sc-hKwDye fcjMZs"><h1 class="sc-furwcr kwcwWd"><a class="sc-crHmcD bNUQy" href="#tag/suit-analytics" aria-label="tag/suit-analytics"></a>suit analytics</h1></div></div></div><div id="tag/suit-analytics/paths/~1suit_analytics/post" data-section-id="tag/suit-analytics/paths/~1suit_analytics/post" class="sc-eCImPb hexvPn"><div class="sc-iCfMLu jTXZbd"><div class="sc-hKwDye fcjMZs"><h2 class="sc-pVTFL QwiBS"><a class="sc-crHmcD bNUQy" href="#tag/suit-analytics/paths/~1suit_analytics/post" aria-label="tag/suit-analytics/paths/~1suit_analytics/post"></a>stream creation<!-- --> </h2><div class="sc-iFMAIt ilWCwE"><div class="sc-iJKOTD sc-cidDSM hGZPxu fYaHea"><p>Stream creation superficial request with detailed analytics parameters description.</p>
<p>Full request description (exclude for this analytics description) available at <code>Luna-Video-Manager</code> documentation.</p>
</div></div><h5 class="sc-iqseJM kyZwVG">Request Body schema: <span class="sc-cCcXHH hgRRjA">application/json</span></h5><div class="sc-iJKOTD sc-cidDSM hGZPxu fYaHea"></div><table class="sc-hGPBjI wbnF"><tbody><tr class=""><td class="sc-hBUSln sc-fFeiMQ sc-gSQFLo kCCtqV fwnkRw iFIEVq" kind="field" title="data"><span class="sc-ieecCq fMdASL"></span><button aria-label="expand properties"><span class="property-name">data</span><svg class="sc-egiyK cUKGZj" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button><div class="sc-nVkyK sc-hiwPVj eyZzuM eSyGZd">required</div></td><td class="sc-bkkeKt fOwWgB"><div><div><span class="sc-lbhJGD sc-iNGGcK gHbDui eLMLxg"></span><span class="sc-lbhJGD sc-jeraig gHbDui ifsMbX">object</span></div> <div><div class="sc-iJKOTD sc-cidDSM hGZPxu fhPxtY"><p>stream data</p>
</div></div></div></td></tr><tr class=""><td class="sc-hBUSln sc-fFeiMQ sc-gSQFLo kCCtqV fwnkRw iFIEVq" kind="field" title="analytics"><span class="sc-ieecCq fMdASL"></span><button aria-label="expand properties"><span class="property-name">analytics</span><svg class="sc-egiyK cUKGZj" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button><div class="sc-nVkyK sc-hiwPVj eyZzuM eSyGZd">required</div></td><td class="sc-bkkeKt fOwWgB"><div><div><span class="sc-lbhJGD sc-iNGGcK gHbDui eLMLxg">Array of </span><span class="sc-lbhJGD sc-jeraig gHbDui ifsMbX">objects</span></div> <div><div class="sc-iJKOTD sc-cidDSM hGZPxu fhPxtY"><p>analytics list</p>
</div></div></div></td></tr><tr class="last "><td class="sc-hBUSln sc-fFeiMQ kCCtqV fwnkRw" kind="additionalProperties" title="property name*"><span class="sc-ieecCq fMdASL"></span><span class="property-name">property name*</span><div class="sc-nVkyK sc-hiwPVj sc-ehCJOs eyZzuM eSyGZd JLmOC">additional property</div></td><td class="sc-bkkeKt fOwWgB"><div><div><span class="sc-lbhJGD sc-iNGGcK gHbDui eLMLxg"></span><span class="sc-lbhJGD sc-jeraig gHbDui ifsMbX">any</span></div> <div><div class="sc-iJKOTD sc-cidDSM hGZPxu fhPxtY"></div></div></div></td></tr></tbody></table><div><h3 class="sc-gjNHFA bCWegr"> Callbacks </h3><button class="sc-xiLah hrnNnQ sc-bQtKYq fuWZWu"><span type="post" class="sc-ilfuhL sc-jHkVzv eXEyiA iMeny operation-type post">post</span><svg class="sc-egiyK gHElpb" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><span class="sc-dVNjXY ixwUsq">{$request.body.callbacks.*.url}</span></button></div></div><div class="sc-jRQBWg sc-gKclnd vSqXI dKDnzb"><div class="sc-FNXRL jYAhkx"><button class="sc-eFegNN bxamQ"><span type="post" class="sc-fmBCVi huMmnt http-verb post">post</span><span class="sc-jWUzzU hxZHzL">/suit_analytics</span><svg class="sc-egiyK laPRNF" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button><div aria-hidden="true" class="sc-lkgTHX fuqHqW"><div class="sc-jlRLRk fUpXCf"><div class="sc-iJKOTD sc-cidDSM hGZPxu fhPxtY"></div><div tabindex="0" role="button"><div class="sc-dUbtfd ggmziU"><span></span>/suit_analytics</div></div></div></div></div><div><h3 class="sc-kDTinF hwMGJl"> <!-- -->Request samples<!-- --> </h3><div class="sc-cxpSdN eCLDJY" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="react-tabs-0" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-1" tabindex="0" data-rttab="true">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-1" aria-labelledby="react-tabs-0"><div><div class="sc-AjmGg bcpfGN"><span class="sc-cNKqjZ iYcnri">Content type</span><div class="sc-bBHHxi eidhyg">application/json</div></div><div class="sc-jgrJph iUheaL"><div class="sc-jObWnj jOBPNj"><div class="sc-giYglK gjxyHD"><button><div class="sc-caiLqq ddxnzs">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class="sc-iJKOTD hGZPxu sc-dPiLbb sRoPx"><div class="redoc-json"><code><button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"data"</span>: <button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"reference"</span>: <span class="token string">"rtsp://stream_url"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"type"</span>: <span class="token string">"stream"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"downloadable"</span>: <span class="token boolean">false</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"analytics"</span>: <button class="collapser" aria-label="collapse"></button><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable collapsed"><button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"analytic_name"</span>: <span class="token string">"suit_analytics"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"parameters"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"parameters"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"probe_count"</span>: <span class="token number">2</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"image_retain_policy"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"mimetype"</span>: <span class="token string">"PNG"</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"roi"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"mode"</span>: <span class="token string">"abs"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"x"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"y"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"width"</span>: <span class="token number">1000</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"height"</span>: <span class="token number">500</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"callbacks"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable collapsed"><button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"url"</span>: <span class="token string">"</span><a href="http://127.0.0.1:8001">http://127.0.0.1:8001</a><span class="token string">"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"type"</span>: <span class="token string">"http"</span></div></li></ul><span class="token punctuation">}</span>,</div></li><li><div class="hoverable collapsed"><button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"url"</span>: <span class="token string">"</span><a href="http://127.0.0.1:8002">http://127.0.0.1:8002</a><span class="token string">"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"type"</span>: <span class="token string">"http"</span></div></li></ul><span class="token punctuation">}</span>,</div></li><li><div class="hoverable collapsed"><button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"type"</span>: <span class="token string">"luna-ws-notification"</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">]</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"targets"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable collapsed"><span class="token string">"suit"</span>,</div></li><li><div class="hoverable collapsed"><span class="token string">"overview"</span></div></li></ul><span class="token punctuation">]</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">]</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-kDTinF hwMGJl"> Callback payload samples </h3><div class="sc-eXlEPa hNivrs"><div class="sc-AjmGg bcpfGN"><span class="sc-cNKqjZ iYcnri">Callback</span><div class="sc-bBHHxi eidhyg">POST: {$request.body.callbacks.*.url}</div></div><div class="sc-fmciRz mgliX"><div class="sc-AjmGg bcpfGN"><span class="sc-cNKqjZ iYcnri">Content type</span><div class="sc-bBHHxi eidhyg">application/json</div></div><div class="sc-jgrJph iUheaL"><div class="sc-jObWnj jOBPNj"><div class="sc-giYglK gjxyHD"><button><div class="sc-caiLqq ddxnzs">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class="sc-iJKOTD hGZPxu sc-dPiLbb sRoPx"><div class="redoc-json"><code><button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"event_type"</span>: <span class="token string">"suit"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"account_id"</span>: <span class="token string">"557d54ec-29ad-4f3c-93b4-c9092ef12515"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"event_create_time"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"event_end_time"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"event"</span>: <button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"stream_id"</span>: <span class="token string">"557d54ec-29ad-4f3c-93b4-c9092ef12515"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"event_id"</span>: <span class="token string">"557d54ec-29ad-4f3c-93b4-c9092ef12515"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"track_id"</span>: <span class="token string">"557d54ec-29ad-4f3c-93b4-c9092ef12515"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"video_segment"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"start_time_offset"</span>: <span class="token number">0.123</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"end_time_offset"</span>: <span class="token number">1.234</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"overview"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"time_offset"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"image"</span>: <span class="token keyword">null</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"location"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"city"</span>: <span class="token string">"Moscow"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"area"</span>: <span class="token string">"Central"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"district"</span>: <span class="token string">"Basmanny"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"street"</span>: <span class="token string">"Podsosensky lane"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"house_number"</span>: <span class="token string">"23 bldg.3"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"geo_position"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"longitude"</span>: <span class="token number">36.616</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"latitude"</span>: <span class="token number">55.752</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div class="sc-eldieg bLydqy"></div></div></div>
<script>
const __redoc_state = {"menu":{"activeItemIdx":-1},"spec":{"data":{"openapi":"3.0.0","info":{"version":"v.0.0.1","title":"Suit Analytics","description":"`Suit Analytics` is intended for determination of:\n - suit_class parameter;\n \n\nThe `suit` analytics decode stream frames taking into account `rate` parameter\nfrom <a href=\"#tag/suit-analytics/paths/~1suit_analytics/post\">stream creation request analytic parameters</a>\nand performs the following actions:\n - Calculates suit class\n - For each callback check it's conditions and execute callbacks, which fit conditions.\n\n The schema of data sending by `callback` described as stream creation request callback.\n"},"components":{"schemas":{"error":{"type":"object","properties":{"error_code":{"type":"integer","description":"Error code."},"desc":{"type":"string","description":"Short error description."},"detail":{"type":"string","description":"Error details."},"link":{"type":"string","description":"Link to the documentation website with the error description."}},"required":["error_code","detail","desc","link"],"example":{"error_code":1,"detail":"internal server error","desc":"internal server error","link":"https://docs.visionlabs.ai/info/luna/troubleshooting/errors-description/code-1"}},"int01":{"type":"integer","enum":[0,1]},"roi_int_coordinates":{"type":"integer","minimum":0,"default":0,"maximum":65536,"example":3327},"roi_float_percent":{"type":"number","format":"float","minimum":0,"default":0,"maximum":100,"example":87.4},"roi_float_percent_size":{"type":"number","format":"float","minimum":0.00001,"maximum":100,"example":66.3},"roi_abs":{"type":"object","properties":{"x":{"$ref":"#/components/schemas/roi_int_coordinates"},"y":{"$ref":"#/components/schemas/roi_int_coordinates"},"width":{"$ref":"#/components/schemas/roi_int_coordinates"},"height":{"$ref":"#/components/schemas/roi_int_coordinates"},"mode":{"type":"string","enum":["abs"],"example":"abs","description":"Coordinates and size are set in pixels."}},"required":["x","y","width","height","mode"]},"roi_percent":{"type":"object","properties":{"x":{"$ref":"#/components/schemas/roi_float_percent"},"y":{"$ref":"#/components/schemas/roi_float_percent"},"width":{"$ref":"#/components/schemas/roi_float_percent_size"},"height":{"$ref":"#/components/schemas/roi_float_percent_size"},"mode":{"type":"string","enum":["percent"],"example":"percent","description":"Coordinates and size are set in percentage."}},"required":["x","y","width","height","mode"]},"roi":{"oneOf":[{"$ref":"#/components/schemas/roi_abs"},{"$ref":"#/components/schemas/roi_percent"}],"description":"Region of interest on a frame. Boundaries of the area are described in `x`, `y` coordinates\nof the top left point and `width`, `height` properties\n**Region must not be any bigger than the original frame**\n\nThe region of interest will be sent to estimator. The smaller the `roi`, the smaller the area the estimator\nwill process and, accordingly, work faster.\n"},"number01":{"type":"number","minimum":0,"maximum":1},"rate":{"type":"object","description":"Analytic rate execution determines on which frames the analytics will be launched.\n\nAnalytic rate execution can be configured in next ways:\n- execute analytic on each Nth frame (`unit` - `frame`)\n- execute analytic on each frame corresponding to the Nth second (`unit` - `second`)\n","properties":{"period":{"type":"float","minimum":0,"default":1,"description":"Period length."},"unit":{"type":"string","default":"second","enum":["frame","second"],"description":"Unit for a period calculation (every `n` seconds or every `n` frames)."}},"required":["unit","period"]},"callback_base":{"type":"object","properties":{"enable":{"type":"integer","enum":[0,1],"default":1,"description":"Whether callback enabled or not."}}},"callback_ws":{"allOf":[{"$ref":"#/components/schemas/callback_base"},{"type":"object","properties":{"type":{"type":"string","enum":["luna-ws-notification"],"description":"Event will be sent with `suit` type."}}}],"required":["type"]},"callback_basic_authorization":{"type":"object","properties":{"type":{"type":"string","description":"Authorization type.","enum":["basic"]},"login":{"type":"string","maxLength":128,"description":"Login."},"password":{"type":"string","maxLength":128,"description":"Password."}},"required":["type","login","password"],"description":"Callback basic authorization parameters."},"callback_http":{"allOf":[{"$ref":"#/components/schemas/callback_base"},{"type":"object","properties":{"type":{"type":"string","enum":["http"],"description":"Event will be sent to http url."},"url":{"type":"string","description":"Callback url."},"authorization":{"$ref":"#/components/schemas/callback_basic_authorization"},"params":{"type":"object","properties":{"timeout":{"type":"integer","default":60,"description":"Callback request timeout."},"content_type":{"type":"string","default":"application/json","enum":["application/json"],"description":"Callback request content type."},"headers":{"type":"object","description":"Callback request headers.","additionalProperties":true}},"description":"Callback request parameters"}}}],"required":["url","type"]},"callback":{"oneOf":[{"$ref":"#/components/schemas/callback_http"},{"$ref":"#/components/schemas/callback_ws"}],"discriminator":{"propertyName":"type","mapping":{"http":"#/components/schemas/callback_http","luna-ws-notification":"#/components/schemas/callback_ws"}}},"image_retain_policy":{"type":"object","description":"Image retain policy applicable when analytic `overview` target is specified,\nand configures parameters with which will image be saved.\n","properties":{"mimetype":{"type":"string","enum":["PNG","JPEG"],"default":"JPEG","description":"Image format."},"quality":{"allOf":[{"$ref":"#/components/schemas/number01"},{"default":1}],"description":"Image quality, on a scale from 0 (worst) to 1 (best). Has no effect on `PNG`."},"max_size":{"type":"integer","minimum":0,"default":640,"description":"Image max size, in pxl. Neither the width nor the height will exceed this value."}}},"period_event_policy":{"type":"object","properties":{"trigger":{"type":"string","enum":["period"]},"interval":{"type":"float","description":"Event generation period interval.","minimum":0,"default":1}},"required":["trigger"]},"start_event_policy":{"type":"object","properties":{"trigger":{"type":"string","enum":["start"]}},"required":["trigger"]},"end_event_policy":{"type":"object","properties":{"trigger":{"type":"string","enum":["end"]}},"required":["trigger"]},"parameters":{"type":"object","description":"Analytic parameters for stream processing.\n\nThere are default analytic parameters which will be applied for stream processing if no one specified here.\n","properties":{"targets":{"type":"array","default":["suit"],"items":{"type":"string","enum":["suit"]},"description":"Estimations to perform on the video.\n\n`suit` will calculate some `suit` value if specified.\n\n`overview` will add image to events.\n"},"callbacks":{"type":"array","description":"Callbacks parameters.\n\n`http` type callback sends events to the specified url by POST request.\n\n`luna-ws-notification` type callback sends events to websocket using sender.\n","maxItems":10,"items":{"$ref":"#/components/schemas/callback"}},"parameters":{"type":"object","description":"Estimation parameters.","properties":{"event_policy":{"description":"Event policy.\n\nWhen suit appears on frame, the new `track` will starts, when suit disappears, `track` will stops.\n\n- when track starts (`trigger` - `start`)\n- when track ends (`trigger` - `end`)\n- periodically while track exists (`trigger` - `period`)\n","type":"object","properties":null,"oneOf":[{"$ref":"#/components/schemas/period_event_policy"},{"$ref":"#/components/schemas/start_event_policy"},{"$ref":"#/components/schemas/end_event_policy"}],"discriminator":{"propertyName":"trigger","mapping":{"period":"#/components/schemas/period_event_policy","start":"#/components/schemas/start_event_policy","end":"#/components/schemas/end_event_policy"}},"default":{"trigger":"start"}},"image_retain_policy":{"$ref":"#/components/schemas/image_retain_policy"},"probe_count":{"description":"A number of consecutive suit estimations with not 'none' class. This parameter is intended to\nprevent false positives from analytics.\n","type":"integer","minimum":0,"default":3},"roi":{"$ref":"#/components/schemas/roi"},"rate":{"allOf":[{"$ref":"#/components/schemas/rate"},{"default":{"unit":"second","period":1}}]}}}}},"stream":{"type":"object","description":"Full stream creation schema is available in `Luna-Video-Manager` documentation.\n\nThe presented schema described only analytics parameters.\n","properties":{"data":{"type":"object","description":"stream data","additionalProperties":true},"analytics":{"type":"array","description":"analytics list","items":{"type":"object","description":"analytics","properties":{"analytic_name":{"type":"string","maxLength":36,"pattern":"^[a-zA-Z0-9_\\-]{1,36}$","description":"Analytic name.","enum":["suit_analytics"]},"parameters":{"type":"object","$ref":"#/components/schemas/parameters"}},"required":["analytic_name"]}}},"additionalProperties":true,"required":["data","analytics"]},"string36_nullable":{"type":"string","maxLength":36,"nullable":true},"longitude":{"type":"number","minimum":-180,"maximum":180,"example":36.616},"latitude":{"type":"number","minimum":-90,"maximum":90,"example":55.752},"geo_position":{"type":"object","nullable":true,"description":"Geo position specified by geographic coordinates - longitude and latitude.","properties":{"longitude":{"allOf":[{"$ref":"#/components/schemas/longitude"},{"description":"Longitude in degrees."}]},"latitude":{"allOf":[{"$ref":"#/components/schemas/latitude"},{"description":"Latitude in degrees."}]}},"required":["longitude","latitude"],"example":{"longitude":36.616,"latitude":55.752}},"uuid":{"type":"string","format":"uuid","pattern":"^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$","example":"557d54ec-29ad-4f3c-93b4-c9092ef12515"},"video_segment":{"type":"object","properties":{"start_time_offset":{"type":"number","example":0.123,"description":"Start video segment offset(seconds)."},"end_time_offset":{"type":"number","example":1.234,"description":"Eng video segment offset(seconds)."}},"required":["start_time_offset","end_time_offset"]},"callback_request":{"type":"object","properties":{"event_type":{"type":"string","enum":["suit"],"description":"Event type."},"account_id":{"allOf":[{"$ref":"#/components/schemas/uuid"}],"description":"Account id of event."},"event_create_time":{"type":"string","description":"Event creation time."},"event_end_time":{"type":"string","description":"Event end time."},"event":{"type":"object","description":"Event.","properties":{"stream_id":{"allOf":[{"$ref":"#/components/schemas/uuid"}],"description":"Stream ID."},"event_id":{"allOf":[{"$ref":"#/components/schemas/uuid"}],"description":"Event ID."},"track_id":{"allOf":[{"$ref":"#/components/schemas/uuid"}],"description":"Track id.","nullable":true},"video_segment":{"$ref":"#/components/schemas/video_segment"},"overview":{"type":"object","properties":{"time_offset":{"type":"number","description":"time offset"},"image":{"type":"str","description":"link to image"}},"required":["time_offset"]},"location":{"type":"object","properties":{"city":{"allOf":[{"$ref":"#/components/schemas/string36_nullable"}],"example":"Moscow","description":"City that stream belongs."},"area":{"allOf":[{"$ref":"#/components/schemas/string36_nullable"}],"example":"Central","description":"Area that stream belongs."},"district":{"allOf":[{"$ref":"#/components/schemas/string36_nullable"}],"example":"Basmanny","description":"District that stream belongs."},"street":{"allOf":[{"$ref":"#/components/schemas/string36_nullable"}],"example":"Podsosensky lane","description":"Street that stream belongs."},"house_number":{"allOf":[{"$ref":"#/components/schemas/string36_nullable"}],"example":"23 bldg.3","description":"Street that stream belongs."},"geo_position":{"$ref":"#/components/schemas/geo_position"}},"description":"Stream location parameters.\n\nRequired callback `location` target to send this value.\n"}},"required":["stream_id","event_id","suit_class","track_id"]}},"required":["event_type","event","account_id","event_create_time","event_end_time"]}}},"paths":{"/suit_analytics":{"post":{"tags":["suit analytics"],"summary":"stream creation","description":"Stream creation superficial request with detailed analytics parameters description.\n\nFull request description (exclude for this analytics description) available at `Luna-Video-Manager` documentation.\n","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/stream"},"example":{"data":{"reference":"rtsp://stream_url","type":"stream","downloadable":false},"analytics":[{"analytic_name":"suit_analytics","parameters":{"parameters":{"probe_count":2,"image_retain_policy":{"mimetype":"PNG"},"roi":{"mode":"abs","x":0,"y":0,"width":1000,"height":500}},"callbacks":[{"url":"http://127.0.0.1:8001","type":"http"},{"url":"http://127.0.0.1:8002","type":"http"},{"type":"luna-ws-notification"}],"targets":["suit","overview"]}}]}}}},"callbacks":{"onData":{"{$request.body.callbacks.*.url}":{"post":{"requestBody":{"description":"subscription payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/callback_request"}}}}}}}}}}}}},"searchIndex":{"store":["tag/suit-analytics","tag/suit-analytics/paths/~1suit_analytics/post"],"index":{"version":"2.3.9","fields":["title","description"],"fieldVectors":[["title/0",[0,0.693,1,0.182]],["description/0",[]],["title/1",[2,0.182,3,0.182]],["description/1",[1,0.196,2,0.129,3,0.129,4,0.492,5,0.744,6,0.492,7,0.492,8,0.897,9,0.492,10,0.492,11,0.492,12,0.492,13,0.492,14,0.492]]],"invertedIndex":[["analyt",{"_index":1,"title":{"0":{}},"description":{"1":{}}}],["avail",{"_index":11,"title":{},"description":{"1":{}}}],["creation",{"_index":3,"title":{"1":{}},"description":{"1":{}}}],["descript",{"_index":8,"title":{},"description":{"1":{}}}],["detail",{"_index":6,"title":{},"description":{"1":{}}}],["document",{"_index":13,"title":{},"description":{"1":{}}}],["exclud",{"_index":10,"title":{},"description":{"1":{}}}],["full",{"_index":9,"title":{},"description":{"1":{}}}],["luna-video-manag",{"_index":12,"title":{},"description":{"1":{}}}],["paramet",{"_index":7,"title":{},"description":{"1":{}}}],["request",{"_index":5,"title":{},"description":{"1":{}}}],["stream",{"_index":2,"title":{"1":{}},"description":{"1":{}}}],["suit",{"_index":0,"title":{"0":{}},"description":{}}],["suit_analyt",{"_index":14,"title":{},"description":{"1":{}}}],["superfici",{"_index":4,"title":{},"description":{"1":{}}}]],"pipeline":[]}},"options":{}};
var container = document.getElementById('redoc');
Redoc.hydrate(__redoc_state, container);
</script>
</body>
</html>
The *events.py* module in *suit_nodes* path contains events and track structures.
import attr
from video_analytics_helpers.containers import (
AggregatedEvent as BaseAggregatedEvent,
Event as BaseEvent,
Overview,
Track as BaseTrack,
)
@attr.dataclass(slots=True)
class Suit:
"""Template event frame result."""
name: str = ""
def asDict(self):
"""Build results in API format."""
return {"name": self.name}
@attr.dataclass(slots=True)
class AggregatedSuitEvent(BaseAggregatedEvent[Overview]):
"""Aggregated suit event."""
eventType: str = "suit"
suit: Suit | None = None
def asDict(self):
"""Build results in API format."""
result = super().asDict()
if self.suit is not None:
result.update(self.suit.asDict())
return result
@attr.dataclass(slots=True)
class SuitEvent(BaseEvent[AggregatedSuitEvent]):
"""Suit event result."""
eventType: str = "suit"
suit: Suit | None = None
def asDict(self):
"""Build event data in API format."""
res = super().asDict()
if self.suit is not None:
res.update(self.suit.asDict())
return res
class SuitTrack(BaseTrack[SuitEvent, AggregatedSuitEvent]):
"""Analytics track."""
The *suit_estimator.py* module in *suit_nodes* path contains suit estimator responsible for image preprocessing, processing and neural model results postprocessing.
from pathlib import Path
import cv2
import numpy as np
import ujson
from video_analytics_helpers.onnxruntime_wrapper import OnnxruntimeWrapper
class SuitClassifier(object):
def __init__(
self,
onnxWrapper: OnnxruntimeWrapper,
crop_size: int = 224,
crop_type: str = "full_frame",
):
self.onnxWrapper = onnxWrapper
self.onnx_input_keys = [t.name for t in self.onnxWrapper.onnxSession.get_inputs()]
self.is_new_id = dict()
self.crop_size = crop_size
self.crop_type = crop_type
self.suit_id = dict()
self._labels = None
async def crop_and_scale(self, img_rgb):
assert img_rgb.shape[2] == 3
img_shape = img_rgb.shape[:2]
if self.crop_type == "center_crop":
min_side = min(img_shape[0], img_shape[1])
y_0, x_0 = (img_shape[0] - min_side) // 2, (img_shape[1] - min_side) // 2
y_1, x_1 = y_0 + min_side, x_0 + min_side
crop = img_rgb[y_0:y_1, x_0:x_1]
elif self.crop_type == "full_frame":
crop = img_rgb.copy()
else:
raise Exception(f"Unknown type of crop: {self.crop_type}")
crop_resized = await self.onnxWrapper.runAsync(cv2.resize, crop, (self.crop_size, self.crop_size))
return crop_resized
async def preproc(self, img_rgb):
crop_prepared = await self.crop_and_scale(img_rgb)
crop_prepared = crop_prepared.astype(np.float32) / 255.0
crop_prepared = (crop_prepared - 0.5) / 0.5
batch = crop_prepared.reshape((1,) + crop_prepared.shape)
batch = batch.transpose(0, 3, 1, 2)
return np.ascontiguousarray(batch)
async def forward(self, img_rgb):
batch_numpy = await self.preproc(img_rgb)
input_tensors_list = {self.onnx_input_keys[0]: batch_numpy}
net_outs = await self.onnxWrapper.forward(input_tensors_list)
return net_outs[0][0]
def get_labels(self):
if self._labels is None:
with open(Path(__file__).parent.parent / "data" / "imagenet-simple-labels.json") as f:
data = ujson.load(f)
self._labels = np.asarray(data)
return self._labels
async def run(self, img_rgb, cam_id):
if not cam_id in self.suit_id:
self.suit_id[cam_id] = 0
self.is_new_id[cam_id] = True
preds = await self.forward(img_rgb)
idx = np.argmax(preds)
return self.get_labels()[idx]
The *suit_nodes.py* module in *suit_nodes* path contains suit nodes structures.
import uuid
from luna_analytics_manager import Node
from luna_analytics_manager.sync import Syncer2 as Syncer
from luna_analytics_manager.templates.image_helpers.roi import getROIRect
from luna_analytics_manager.templates.models import EventStatus, VideoSegment
from video_analytics_helpers.frame_base_analytics import BaseFrameAnalyticsNode
from video_analytics_helpers.node_collections import NodeOverview as NodeOverviewBase
from ..analytics import AnalyticsCtx, FrameCtx
from ..classes import FrameRes
from .events import AggregatedSuitEvent, Overview, Suit, SuitEvent, SuitTrack
from .suit_estimator import SuitClassifier
class TrackFrame:
bestScore: float | None
bestTimestamp: float | None
class NodeSuit(
BaseFrameAnalyticsNode[
FrameCtx,
AnalyticsCtx,
SuitEvent,
AggregatedSuitEvent,
FrameRes,
Suit,
SuitTrack,
]
):
"""Suit Analytics node."""
name = "suit"
requires: list[Node] = []
frameResCls = FrameRes
# class for tracks
trackCls = SuitTrack
def __init__(self):
super().__init__()
self.frameSyncer = Syncer()
self.track = None
self.trackLength = 0
self.suitEstimator = SuitClassifier(onnxWrapper=AnalyticsCtx.onnxWrapper)
def buildEvent(self, suit: Suit, frameCtx: FrameCtx, ctx: AnalyticsCtx) -> tuple[SuitEvent, AggregatedSuitEvent]:
"""Generate template current event and aggregated event."""
event = SuitEvent(suit=suit, timeOffset=frameCtx.timestamp)
aggregatedEvent = AggregatedSuitEvent(
videoSegment=VideoSegment(frameCtx.timestamp, frameCtx.timestamp),
suit=suit,
overview=Overview(frameCtx.timestamp, frameCtx.image),
)
event.eventId = aggregatedEvent.eventId = str(uuid.uuid4())
return event, aggregatedEvent
def updateEvent(self, suit: Suit, frameCtx: FrameCtx, ctx: AnalyticsCtx):
"""Update current event with new frame."""
event = self.track.currentEvent
event.suit = suit
event.timeOffset = frameCtx.timestamp
event.eventStatus = EventStatus.inProcess
def updateAggregatedEvent(self, suit: Suit, frameCtx: FrameCtx, ctx: AnalyticsCtx):
"""Update current aggregated event with new frame."""
aggregatedEvent = self.track.currentAggregatedEvent
if aggregatedEvent.videoSegment is None:
aggregatedEvent.videoSegment = VideoSegment(frameCtx.timestamp, frameCtx.timestamp)
else:
aggregatedEvent.videoSegment.endTimeOffset = frameCtx.timestamp
if aggregatedEvent.suit is None:
aggregatedEvent.suit = suit
async def estimate(self, frameCtx: FrameCtx, ctx: AnalyticsCtx) -> Suit | None:
img_rgb = frameCtx.image.asNPArray() # check if it is really RGB format
if ctx.params.roi:
roi = getROIRect(frameCtx.image.rect, ctx.params.roi)
img_rgb = img_rgb[roi.top : roi.bottom, roi.left : roi.right]
camera_id = "0"
detectedObj = await self.suitEstimator.run(img_rgb, camera_id)
return Suit(name=detectedObj)
def isTrackNeedEnd(self, suit: Suit, frameCtx: FrameCtx, ctx: AnalyticsCtx):
"""
Determinate that event is needed to end or not after current frame processing.
If function return `True`, track will be stopped. If function return `False` track will be started or continue.
"""
return suit.name != "suit"
class NodeOverview(NodeOverviewBase[FrameCtx, AnalyticsCtx]):
"""Overview node."""
requires = [NodeSuit]
Agent lambda examples
Here is an example of agent lambda with suit video analytics:
To include video analytics presented in previous chapter into lambda agent as package it needs the following lambda archive file structure:
Lambda agent with suit analytics example file structure (the case when analytics includes in lambda as package)├──pyproject.toml ├──poetry.lock └──lambda_main.py
The lambda_main.py module:
lambda_main.pyAVAILABLE_ANALYTICS = ["suit_analytics"]
It this case, the dependencies of lambda agent must include only analytics as dependency, not analytics dependencies:
*pyproject.toml*
pyproject.toml[build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.poetry] name = "lambda-agent" version = "0.0.1" description = "lambda-agent" authors = [ "VisionLabs", ] [[tool.poetry.source]] name = "vlabspypi" url = "http://pypi.visionlabs.ru/root/public/+simple" priority = "primary" [[tool.poetry.source]] name = "public-pypi" url = "https://pypi.org/simple" priority = "supplemental" [tool.poetry.dependencies] python = "^3.12" suit-analytics = "0.0.4"
To include presented analytics into lambda agent as module it needs the following lambda archive file structure:
Lambda agent with suit analytics example file structure (the case when analytics includes in lambda as module)├──pyproject.toml ├──poetry.lock ├──lambda_main.py └──suit_analytics ├──__init__.py ├──analytics.py ├──classes.py ├──common.py ├──models.py ├──spec.html ├──spec.yml └──data ├──resnet50-v2-7.onnx └──imagenet-simple-labels.json └──suit_nodes ├──__init__.py ├──events.py ├──suit_estimator.py └──suid_nodes.py
In this case, the analytics dependencies must be included into lambda dependencies. The lambda dependencies described as pyproject.toml file:
*pyproject.toml*
pyproject.toml[build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.poetry] name = "lambda agent" version = "0.0.1" description = "lambda agent" authors = [ "VisionLabs", ] [[tool.poetry.source]] name = "vlabspypi" url = "http://pypi.visionlabs.ru/root/public/+simple" priority = "primary" [[tool.poetry.source]] name = "public-pypi" url = "https://pypi.org/simple" priority = "supplemental" [tool.poetry.dependencies] python = "^3.12" luna-analytics-manager = {version="*", extras=["sdk"]} opencv-python-headless = "*" nvidia-curand-cu11 = "*" nvidia-cufft-cu11 = "*" onnxruntime-gpu = "*" pynvcodec = "^2.6.0"