MQTT API
This page is the reference for what the Embedded Stream Processor publishes and accepts over MQTT. If the device is a Face Matcher edge stream, you do not use these topics directly: the edge-stream-processor service consumes FrameData, Station manages configuration and licensing, and edge-streams-state-synchronizer fills the on-device watchlist; you consume results through Face Matcher's REST, GraphQL and RabbitMQ notifications. The topics below are for standalone integrations that run their own broker and consumer, and for debugging.

Topic layout
Every topic is prefixed with the root topic and client ID from the device's connection section (<topic>/<client_id>/..., for example edge-stream/lobby-cam-1/frame_data). The device speaks MQTT v3, which has no native request/response pattern, so responses to a request topic are published on the same topic with a /response suffix; where the topic allows it, append your own :request_id segment to tell concurrent requests apart (the response is published on .../<request_id>/response). Message payloads use the format set in messaging.format — Protobuf (default), JSON or YAML.
Topic (after <topic>/<client_id>/) | Direction | Kind | Payload |
|---|---|---|---|
frame_data | device → you | Periodic | FrameData |
health | device → you | Retained | HealthReport |
settings | device → you | Retained | current settings |
settings/update | you → device | Retained | new settings to apply at run time |
log | device → you | Periodic (every 5 s) | log lines as text |
user | request / response | Response | Records |
user/<user_id> | request / response | Response | Record |
user/<user_id>/face/add[/<request_id>] | request / response | Response | image or template in, Record out |
user/<user_id>/metadata/<key>/<value>[/<request_id>] | request / response | Response | empty in, empty out |
user/<user_id>/delete[/<request_id>] | request / response | Response | empty in, empty out |
db/update[/<request_id>] | request / response | Response | UpdateRequest in, UpdateResponse out |
db/status[/<request_id>] | request / response | Response | empty in, StatusResponse out |
Message kinds: Retained messages are kept by the broker and delivered to new subscribers; Periodic messages are published on the device's schedule; Response messages answer a request and carry either the operation's result or an Error with a message string.
Health
The retained health message is updated on every status change: Online right after connecting, Offline when the stream processor restarts (for example after a settings change) or shuts down cleanly, and OfflineDisconnected as the broker-delivered last will when the connection drops unexpectedly. The report also carries the device software version. Station's edge-stream health indicator is derived from this message.
FrameData
frame_data is the primary output. The device publishes it whenever a new face is detected and at least every messaging.interval milliseconds while faces are tracked (and continuously when allow_empty_messages is on). Its content follows the device settings: crops only with crop.enable, templates only with face_extraction.enable, and so on. All coordinates are normalized to the frame (0.0–1.0).
| Message | Fields |
|---|---|
FrameData | timestamp, frame_number, frame_image (Image, data only with full_frame.enable), face_data[], lost_object_data[], client_id |
FaceData | face_detection_data, face_tracking_data, landmarks_data[] (23 keypoints), face_mask_data, cropping_data, liveness_data[], template_data, identification_data[], identification (whether identification ran) |
FaceDetectionData | bounding_box (x, y, width, height), detection_confidence (normalized), raw_detection_confidence |
TrackingData | tracking_id, tracking_uuid, tracking_state (New, Tracked, Lost, Removed) |
FaceLandmarkData | keypoint_type (eye corners and centres, nose, mouth, eyebrows, face edges, chin), confidence, x, y |
FaceMaskData | confidence — mask present when above about 0.5 |
CroppingData | crop_extension, crop_box, crop_image (Image) |
LivenessData | liveness_type (PassiveDistant, PassiveNearby), score, raw_score, liveness_conditions[] (condition_type, value, thresholds, condition_met), conditions_met |
TemplateData | template (bytes, 522 B) |
IdentificationData | uuid of the matched record, score (0.0–1.0), dsid (database state ID), meta_data[] key/value pairs |
LostObjectData | tracking_uuid, first_time_appeared, last_time_appeared — emitted once when a tracklet is closed |
Image | width, height, data (bytes), image_format (Raw, Jpeg, Png) |
The authoritative Protobuf definitions (innovatrics.embedded.stream_processor.frame_data, common, health, db, user) ship with the device package; they are also reproduced in the Embedded Toolkit stream-processor messaging reference. Compile them with protoc for your language and subscribe to frame_data to decode messages.
A trimmed JSON FrameData (messaging.format: Json) with one identified face; landmarks, most keypoints and the image bytes are elided:
{
"timestamp": { "seconds": 1688110510, "nanos": 440625640 },
"frame_number": 200,
"frame_image": { "width": 1280, "height": 720, "data": null, "image_format": 0 },
"face_data": [
{
"face_detection_data": {
"bounding_box": { "x": 0.619, "y": 0.343, "width": 0.133, "height": 0.302 },
"detection_confidence": 0.247,
"raw_detection_confidence": 0.992
},
"face_tracking_data": {
"tracking_id": 3,
"tracking_uuid": "c8052a96-8c9d-4765-be94-1d6282cf4b32",
"tracking_state": 1
},
"landmarks_data": [
{ "keypoint_type": 0, "confidence": 0.66, "x": 0.645, "y": 0.457 },
{ "keypoint_type": 1, "confidence": 0.77, "x": 0.655, "y": 0.456 }
],
"face_mask_data": { "confidence": 0.02 },
"cropping_data": {
"crop_extension": 2,
"crop_box": { "x": 0.521, "y": 0.231, "width": 0.319, "height": 0.567 },
"crop_image": { "width": 250, "height": 250, "data": "...", "image_format": 1 }
},
"liveness_data": [
{ "liveness_type": 0, "score": 0.739, "raw_score": 0.256, "liveness_conditions": [], "conditions_met": true }
],
"template_data": { "template": "SUNGADFY/PwKA..." },
"identification_data": [
{ "uuid": "b8f21151-0c8e-4c6a-8f29-882b405ff9aa", "score": 0.416, "dsid": "GEN_ID_dbd738aa||2", "meta_data": [] }
],
"identification": true
}
],
"lost_object_data": [],
"client_id": "lobby-cam-1"
}
User topics
The device keeps a local database of records — a UUID, one or more face templates and key/value metadata — in the file set by face_identification.storage, and matches every extracted template against it when face_identification.enable is true. The user topics manage single records. <user_id> is a UUID v4 that you choose; adding a face to an unknown UUID creates the record. Examples use the mqtt CLI client against a broker at 192.0.2.10 and a device with client ID lobby-cam-1.
Add a face — payload is a JPEG or PNG image, from which the device extracts a template with its own settings, or an already extracted template file. The response is the updated Record:
$ mqtt pub -h 192.0.2.10 -t edge-stream/lobby-cam-1/user/4af8b586-1361-4ff2-9a58-034aa9a03c0f/face/add -m:file face_image.jpg
$ mqtt pub -h 192.0.2.10 -t edge-stream/lobby-cam-1/user/4af8b586-1361-4ff2-9a58-034aa9a03c0f/face/add -m:file face_template.icf
Attach metadata — the key and value travel in the topic; the payload is empty. Metadata is returned in identification_data.meta_data whenever the record matches:
$ mqtt pub -h 192.0.2.10 -t edge-stream/lobby-cam-1/user/4af8b586-1361-4ff2-9a58-034aa9a03c0f/metadata/name/Jane -m:empty
List or get records — publish an empty message; the response is Records (all users, with templates and metadata) or one Record:
$ mqtt pub -h 192.0.2.10 -t edge-stream/lobby-cam-1/user -m:empty
$ mqtt pub -h 192.0.2.10 -t edge-stream/lobby-cam-1/user/4af8b586-1361-4ff2-9a58-034aa9a03c0f -m:empty
Delete a record:
$ mqtt pub -h 192.0.2.10 -t edge-stream/lobby-cam-1/user/4af8b586-1361-4ff2-9a58-034aa9a03c0f/delete -m:empty
Subscribe to the matching .../response topic before publishing to see the result, for example mqtt sub -h 192.0.2.10 -t 'edge-stream/lobby-cam-1/user/+/delete/response'.
DB topics
The db topics apply batched changes and track the database state with a DSID (database state ID), a string you choose to label each state. This is the mechanism a server uses to keep a device in sync: it sends the changes that move the device from from_dsid to to_dsid, and the device answers with the DSID it now holds; if the update fails, the device reports failure_dsid instead, so the server knows to resend.
| Topic | Request | Response |
|---|---|---|
db/update[/<request_id>] | UpdateRequest: from_dsid, to_dsid, failure_dsid, clear (wipe the database first), updates[] (MemberUpsert: member_info.uuid, templates[], meta_data[]), deletes[] (MemberDelete: member_info.uuid) | UpdateResponse: current_dsid, optional error_message |
db/status[/<request_id>] | empty | StatusResponse: current_dsid, optional error_message |
Requests are binary Protobuf (or JSON/YAML per messaging.format), so build them from the db.proto definitions rather than by hand. With Face Matcher, edge-streams-state-synchronizer drives these topics from the watchlists you select in Station; the dsid value that then appears in identification_data lets the server tell which watchlist state a match was made against. Templates you push must come from the same extraction algorithm the device uses, otherwise matching returns no candidates.
Settings and log topics
The device publishes its effective configuration as a retained message on settings and applies a new configuration published to settings/update, which is how Station changes edge-stream processing without touching the device's file system. Sections that need a restart (solvers, license, log) restart the stream processor, which briefly reports Offline on health. The log topic carries the device's log lines every five seconds at the configured log.level, in the form <ISO timestamp> <LEVEL> <message>; subscribe to it when you cannot reach the device's own log file:
$ mqtt sub -h 192.0.2.10 -t edge-stream/lobby-cam-1/log
2025-12-16T11:22:32.966382Z DEBUG {client_id="lobby-cam-1"}:detection: Stream detection, detected 1 faces, 1 persons