Skip to main content

Pushing face detections

eConnect's own face recognition writes its sightings into Object Analytics — and so can yours. Push detections from your engine and every downstream feature works on them: subjects and their history, face tags and watch lists, alerts, duration of visit, semantic search, and the video beside each hit.

POST /api/v2/oa/plugins/{pluginInstalledId}/subject-detections

The body is an array, so this is the batch endpoint as well as the single one. It returns true on success.

Register a detector first​

A detector is the thing that did the seeing — your camera, engine or sensor, as eConnect knows it. Every detection names its detector by familiar id, and a detection whose detector does not exist has nowhere to land. Create it once, at install time:

PUT /api/v2/oa/plugins/{pluginInstalledId}/subject-detectors
{
"familiarId": "acme-fr-door-3",
"familiarName": "North Entrance — Door 3",
"detectorType": "Acme FR 2.1",
"timeZone": "America/New_York",
"isDisabled": false
}
FieldWhat it is
familiarIdYour stable identifier. This is the value every detection carries — pick it once and never change it
familiarNameWhat operators see in grids and filters
detectorTypeYour engine's name and version. Useful when several engines feed one site
timeZoneIANA zone (America/New_York), so local-time reporting is right regardless of where the server sits

List what exists with GET .../subject-detectors, map your ids to eConnect's once at start-up, and fail loudly on anything unmapped rather than sending detections into a void.

Detectors can be merged later

If you register a detector twice — a rename, a re-install — an administrator can merge the duplicate into the real one with POST .../subject-detectors/{targetDetectorId}/merge, and the detections move with it. Nothing is lost, but it is work; a stable familiarId avoids it.

The payload​

[
{
"subject": "8f2b6d41-0c3e-4a7a-9d55-1f3f6a2b9e10",
"subjectRef": "d41d8cd9-8f00-3204-a980-0998ecf8427e",
"device": "acme-fr-door-3",
"dataTimeStamp": "2026-10-02T21:14:05Z",
"timeZone": "America/New_York",
"detectorType": "Acme FR 2.1",
"confidence": 0.95,
"age": 41,
"gender": "M"
}
]
FieldWhy it matters
subjectWho was seen — the subject identity eConnect holds. A detection with no subject is an unidentified sighting; with one it joins that person's history
subjectRefThis sighting. Your engine's own immutable detection id, unique per observation. It is the key the close call below uses, and what makes a resend traceable
deviceThe detector's familiarId — in practice the camera the face was seen on
dataTimeStampWhen the face was seen, UTC. Everything time-based — history, alerts, the jump to video — works from this
timeZoneThe detector's IANA zone
detectorTypeThe engine that made the match
confidenceMatch confidence, 0–1. Operators filter on it, so send the real number rather than a constant
ageEstimated age, where your engine produces one
genderA single character: M, F, or U for unknown

Demographics feed the demographics view. Send U rather than a guess, and leave age out if your engine is not estimating it — an invented number is worse than a missing one, because a report will believe it.

Getting the subject id​

Subjects are people, and they live in eConnect. Search for one, or create one, before you can attribute a detection to it:

GET /api/v2/oa/plugins/{pluginInstalledId}/subjects/search?identification=...&maxRecords=10
GET /api/v2/oa/plugins/{pluginInstalledId}/subjects/by-face/{faceId}
PUT /api/v2/oa/plugins/{pluginInstalledId}/subjects

If your engine has its own gallery, create the subject once per person and cache the mapping. If it does not, push the detection without a subject — it is still a sighting on a timeline, still searchable, and an operator can identify it later.

Closing an observation​

A face that lingers in frame is one observation, not two hundred. Insert the detection when the subject appears, then close the segment when they leave, and eConnect derives the duration from the two timestamps — this is what fills in duration of visit for an operator:

POST /api/v2/oa/plugins/{pluginInstalledId}/subject-detections/close
[
{ "subjectRef": "d41d8cd9-8f00-3204-a980-0998ecf8427e", "lastDetectionUtc": "2026-10-02T21:16:48Z" }
]

It is keyed on subjectRef and returns the number of rows updated.

A zero is normal

The close call can legitimately return 0: the row may not have landed yet, or it may have been purged by retention. Treat zero as information, not as an error to retry forever.

Sending it​

curl -X POST \
"https://your-server/api/v2/oa/plugins/$OA_ID/subject-detections" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '[
{
"subject": "8f2b6d41-0c3e-4a7a-9d55-1f3f6a2b9e10",
"subjectRef": "d41d8cd9-8f00-3204-a980-0998ecf8427e",
"device": "acme-fr-door-3",
"dataTimeStamp": "2026-10-02T21:14:05Z",
"timeZone": "America/New_York",
"confidence": 0.95
}
]'
// An engine streams hits; batch what arrived in the last second or two.
var batch = hits.Select(h => new
{
subject = h.GalleryMatch?.EConnectSubjectId, // null for an unidentified sighting
subjectRef = h.DetectionId, // your own immutable id
device = detectorFamiliarId,
dataTimeStamp = h.SeenUtc,
timeZone = "America/New_York",
detectorType = "Acme FR 2.1",
confidence = h.Score,
}).ToArray();

var response = await http.PostAsJsonAsync($"oa/plugins/{oaId}/subject-detections", batch);
response.EnsureSuccessStatusCode();

// Later, when the subject leaves frame:
await http.PostAsJsonAsync($"oa/plugins/{oaId}/subject-detections/close",
new[] { new { subjectRef = hit.DetectionId, lastDetectionUtc = DateTime.UtcNow } });
# Faces carry no images, so batches can be large. A few hundred per call is comfortable.
for chunk in chunked(pending, 200):
resp = requests.post(
f"{BASE}/oa/plugins/{oa_id}/subject-detections",
headers={"Authorization": f"Bearer {token}"},
json=[to_detection(h) for h in chunk],
timeout=60,
)
resp.raise_for_status()
mark_sent(chunk) # only after the server has it
MethodPathWhat it does
POSToa/plugins/{id}/subject-detectionsInsert detections
POSToa/plugins/{id}/subject-detections/closeClose observation segments
GEToa/plugins/{id}/subject-detectionsQuery detections
GEToa/plugins/{id}/subject-detections/demographicsAge and gender views
DELETEoa/plugins/{id}/subject-detections/{detectId}Remove one detection
PUToa/plugins/{id}/subject-detectorsRegister a detector
GEToa/plugins/{id}/subject-detectorsList detectors to map against
GEToa/plugins/{id}/subjects/searchFind a subject before attributing a detection
GEToa/plugins/{id}/subject-tagsWatch lists a detection can match

The full list is on the Object Analytics reference page.

Permissions​

The service account needs the ObjectAnalytics* keys for the operations it performs — detection inserts, plus the detector keys if it registers its own detector at install time — and the Object Analytics plugin instance must be within its logical group. Creating subjects needs more than pushing detections does, so if your engine only reports sightings, grant only that. The FaceRec* keys cover enrolling, merging and splitting, which an ingress account does not need. See the Permission Keys reference.

What operators see​

Pushed detections are ordinary detections. They are found by Face Search, match face tags and raise the alerts configured on them, accumulate as a person's detection history — with the observed duration your close calls produced — and, where the detector is associated with a camera, open the footage of the moment.