DocsIDarsa.technology

Docs / Face Recognition And Liveness SDK / API Reference

ARSA Face SDK API

Face SDK HTTP API v2, served by your own Face SDK server. Authenticate with an API key (x-api-key: <key> or Authorization: Bearer <key>); each key has scopes. Errors are RFC 9457 problem+json documents with a stable code. Operations marked x-edition: prod are not part of the Demo edition.

2.0.1

Recognition

Compare two faces (1:1), check a face against an enrolled person (1:1), find a face among everyone enrolled (1:N).

Find A Face Among Everyone Enrolled (1:N)

POST/v2/identify

Scope identify. Each face is identified (best match at or above the threshold for this gallery size and FPIR), unknown, or inconclusive (quality too poor to search).

Body

FieldTypeDescription
attributesstringComma-separated subset of gender,expression, as for detection.
fpirnumberTarget false positive identification rate (default: tenant policy identify_fpir).
imagerequiredstringProbe image; every face in it is searched. Multipart: an image file part (JPEG, PNG, BMP or WebP). JSON: the image as base64, optionally as a data: URL.
kintegerCandidates per face in investigation mode (1-50, default 5).
modeenuminvestigation: return the top-k candidates instead of a decision. investigation
qualityenumProbe quality policy. default, surveillance

Responses

StatusMeaning
200One result per face
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
413Payload too large
429Rate or licence limit reached
503Busy, retry shortly

Request

curl -s -X POST "http://localhost:8080/v2/identify" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "http://localhost:8080/v2/identify",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/identify", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "face_count": 1,
  "faces": [
    {
      "bbox": {
        "height": 143,
        "width": 102,
        "x": 81,
        "y": 50
      },
      "confidence": 0.9999,
      "landmarks": [
        [
          104,
          112
        ],
        [
          149,
          112
        ],
        [
          124,
          136
        ],
        [
          103,
          155
        ],
        [
          152,
          156
        ]
      ],
      "passes": true,
      "quality": {
        "brightness": 150.2,
        "eye_distance": 45,
        "face_px": 143,
        "pitch": 8.6,
        "roll": 0,
        "sharpness": 111.8,
        "yaw": -5.1
      },
      "reasons": [],
      "result": "identified",
      "similarity": 0.7861,
      "uid": "emp-1042"
    }
  ],
  "gallery_size": 250,
  "model": "face-rec-b",
  "threshold": {
    "fpir": 0.01,
    "method": "scaled",
    "value": 0.3712
  }
}

Compare Two Faces (1:1)

POST/v2/verify

Scope verify. Compares the face of each image. When either image has no face the answer is 422 NO_FACE with the partial comparison in result; more than one face gives 422 MULTIPLE_FACES (see select); a face too poor to compare gives decision: inconclusive.

Body

FieldTypeDescription
farnumberTarget false accept rate of the access profile (default 1e-4; the nearest stricter calibrated point is used).
image1requiredstringFirst image: one face (see select). Multipart: an image file part (JPEG, PNG, BMP or WebP). JSON: the image as base64, optionally as a data: URL.
image2requiredstringSecond image: one face (see select). Multipart: an image file part (JPEG, PNG, BMP or WebP). JSON: the image as base64, optionally as a data: URL.
modelstringEmbedder id (GET /v2/models); default: the server's default embedder.
profileenumOperating-point profile. access, access-relaxed
selectenumBy default an image with more than one face is refused (422 MULTIPLE_FACES). With largest, the largest face is used when it is at least twice the size of the next (for example an ID document with a small second portrait). largest

Responses

StatusMeaning
200The comparison
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
413Payload too large
422Refused (image quality or consistency)
429Rate or licence limit reached
503Busy, retry shortly

Request

curl -s -X POST "http://localhost:8080/v2/verify" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "http://localhost:8080/v2/verify",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/verify", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "decision": "match",
  "faces": {
    "image1": {
      "bbox": {
        "height": 143,
        "width": 102,
        "x": 81,
        "y": 50
      },
      "confidence": 0.9999,
      "landmarks": [
        [
          104,
          112
        ],
        [
          149,
          112
        ],
        [
          124,
          136
        ],
        [
          103,
          155
        ],
        [
          152,
          156
        ]
      ],
      "passes": true,
      "quality": {
        "brightness": 150.2,
        "eye_distance": 45,
        "face_px": 143,
        "pitch": 8.6,
        "roll": 0,
        "sharpness": 111.8,
        "yaw": -5.1
      },
      "reasons": []
    },
    "image2": {
      "bbox": {
        "height": 160,
        "width": 118,
        "x": 64,
        "y": 41
      },
      "confidence": 0.9999,
      "landmarks": [
        [
          104,
          112
        ],
        [
          149,
          112
        ],
        [
          124,
          136
        ],
        [
          103,
          155
        ],
        [
          152,
          156
        ]
      ],
      "passes": true,
      "quality": {
        "brightness": 132.7,
        "eye_distance": 52,
        "face_px": 160,
        "pitch": 4.2,
        "roll": -2,
        "sharpness": 96.4,
        "yaw": 3.3
      },
      "reasons": []
    }
  },
  "match": true,
  "model": "face-rec-b",
  "operating_point": {
    "far": 0.0001,
    "profile": "access",
    "threshold": 0.3479
  },
  "similarity": 0.8312,
  "threshold": 0.3479
}

Check A Face Against A Person (1:1)

POST/v2/verify/identity

Scope verify. 409 NEEDS_REENROLMENT when the identity has no template for the active embedder; 422 NO_FACE when the image has no face, 422 MULTIPLE_FACES when it has more than one (see select).

Body

FieldTypeDescription
farnumberTarget false accept rate (default: tenant policy verify_far).
imagerequiredstringProbe image: one face (see select). Multipart: an image file part (JPEG, PNG, BMP or WebP). JSON: the image as base64, optionally as a data: URL.
profileenumDefault: tenant policy verify_profile. access, access-relaxed
selectenumBy default an image with more than one face is refused (422 MULTIPLE_FACES). With largest, the largest face is used when it is at least twice the size of the next (for example an ID document with a small second portrait). largest
uidrequiredstringThe identity to verify against.

Responses

StatusMeaning
200The comparison against the identity's best template
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found
409Conflict
413Payload too large
422Refused (image quality or consistency)
429Rate or licence limit reached
503Busy, retry shortly

Request

curl -s -X POST "http://localhost:8080/v2/verify/identity" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "http://localhost:8080/v2/verify/identity",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/verify/identity", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "decision": "match",
  "face": {
    "bbox": {
      "height": 143,
      "width": 102,
      "x": 81,
      "y": 50
    },
    "confidence": 0.9999,
    "landmarks": [
      [
        104,
        112
      ],
      [
        149,
        112
      ],
      [
        124,
        136
      ],
      [
        103,
        155
      ],
      [
        152,
        156
      ]
    ],
    "passes": true,
    "quality": {
      "brightness": 150.2,
      "eye_distance": 45,
      "face_px": 143,
      "pitch": 8.6,
      "roll": 0,
      "sharpness": 111.8,
      "yaw": -5.1
    },
    "reasons": []
  },
  "match": true,
  "model": "face-rec-b",
  "operating_point": {
    "far": 0.0001,
    "profile": "access",
    "threshold": 0.3479
  },
  "similarity": 0.7861,
  "threshold": 0.3479,
  "uid": "emp-1042"
}

People And Enrolment

Enrol people, add photos, list and delete them; groups such as watchlists.

List Groups

GET/v2/groups

Scope enrol. Groups of people (a watchlist, VIPs, staff) with an alert level: alert and notice are carried by camera events (groups, alert) so screens and webhook receivers can react; none is a label only.

Responses

StatusMeaning
200The tenant's groups with member counts
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X GET "http://localhost:8080/v2/groups" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/groups",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/groups", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Create A Group

POST/v2/groups

Scope admin. Names are unique per tenant (1-64 characters). Audited (group.create).

Body

FieldTypeDescription
levelenumalert, notice, none
namerequiredstring

Responses

StatusMeaning
200The new group
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
409Conflict

Request

curl -s -X POST "http://localhost:8080/v2/groups" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "http://localhost:8080/v2/groups",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/groups", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Rename A Group Or Change Its Level

PATCH/v2/groups/{id}

Scope admin. Takes effect on the next camera event. Audited (group.update).

Parameters

FieldTypeDescription
idrequired pathstringGroup id.

Body

FieldTypeDescription
levelenumalert, notice, none
namestring

Responses

StatusMeaning
200The group
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found
409Conflict

Request

curl -s -X PATCH "http://localhost:8080/v2/groups/{id}" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.patch(
    "http://localhost:8080/v2/groups/{id}",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/groups/{id}", {
  method: "PATCH",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Delete A Group

DELETE/v2/groups/{id}

Scope admin. Audited (group.delete).

Parameters

FieldTypeDescription
idrequired pathstringGroup id.

Responses

StatusMeaning
200Deleted; its members stay enrolled
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X DELETE "http://localhost:8080/v2/groups/{id}" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.delete(
    "http://localhost:8080/v2/groups/{id}",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/groups/{id}", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

List People

GET/v2/identities

Scope enrol.

Parameters

FieldTypeDescription
prefix querystringOnly uids starting with this.
after querystringUids after this one (next_after of the previous page).
limit queryintegerPage size (1-1000).

Responses

StatusMeaning
200A page of identities, by uid
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X GET "http://localhost:8080/v2/identities" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/identities",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/identities", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Enrol A Person

POST/v2/identities

Scope enrol. Every image must show one face passing the tenant's enrolment policy, and the images must show the same person; otherwise 422 with per-image images: [{image, reasons}] (or INCONSISTENT_ENROLMENT_IMAGES). 409 CONFLICT when the uid exists, 409 POSSIBLE_DUPLICATE (with existing_uid and similarity only for keys that also have the identify scope) when the face is already enrolled under another uid. 429 LICENSE_LIMIT at the licence's identity limit.

Body

FieldTypeDescription
display_namestringOptional display name.
forceboolean | stringtrue: skip the duplicate check (needs the admin scope).
imagerequiredstringEnrolment image; send 1 to 5 images (e.g. image, image2 .. image5), the first 5 are used. Multipart: an image file part (JPEG, PNG, BMP or WebP). JSON: the image as base64, optionally as a data: URL.
selectenumAccept an image with several faces when the largest is at least twice the size of the next. largest
uidrequiredstringIdentity id: 1-128 characters of A-Z a-z 0-9 . _ : @ -.

Responses

StatusMeaning
201Enrolled
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
409Conflict
413Payload too large
422Refused (image quality or consistency)
429Rate or licence limit reached
503Busy, retry shortly

Request

curl -s -X POST "http://localhost:8080/v2/identities" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "http://localhost:8080/v2/identities",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/identities", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Get A Person

GET/v2/identities/{uid}

Scope enrol.

Parameters

FieldTypeDescription
uidrequired pathstringIdentity id.

Responses

StatusMeaning
200The identity and its templates
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X GET "http://localhost:8080/v2/identities/{uid}" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/identities/{uid}",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/identities/{uid}", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Delete A Person

DELETE/v2/identities/{uid}

Scope enrol. From the dashboard this also needs an elevated session; 403 otherwise.

Parameters

FieldTypeDescription
uidrequired pathstringIdentity id.

Responses

StatusMeaning
200Deleted with its templates
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X DELETE "http://localhost:8080/v2/identities/{uid}" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.delete(
    "http://localhost:8080/v2/identities/{uid}",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/identities/{uid}", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Set A Person's Groups

PUT/v2/identities/{uid}/groups

Scope admin. Replaces the list (an empty list removes the person from every group). Audited (identity.groups, group names before and after).

Parameters

FieldTypeDescription
uidrequired pathstringIdentity id.

Body

FieldTypeDescription
groupsrequiredstring[]Group ids.

Responses

StatusMeaning
200The person's groups
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X PUT "http://localhost:8080/v2/identities/{uid}/groups" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.put(
    "http://localhost:8080/v2/identities/{uid}/groups",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/identities/{uid}/groups", {
  method: "PUT",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Add Photos To A Person

POST/v2/identities/{uid}/templates

Scope enrol. Same image checks as enrolment; each new image must also match the identity's existing templates (422 INCONSISTENT_ENROLMENT_IMAGES otherwise).

Parameters

FieldTypeDescription
uidrequired pathstringIdentity id.

Body

FieldTypeDescription
imagerequiredstringAdditional image; send 1 to 5 images, the first 5 are used. Multipart: an image file part (JPEG, PNG, BMP or WebP). JSON: the image as base64, optionally as a data: URL.
selectenumAs for enrolment. largest

Responses

StatusMeaning
201Templates added
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found
413Payload too large
422Refused (image quality or consistency)
429Rate or licence limit reached
503Busy, retry shortly

Request

curl -s -X POST "http://localhost:8080/v2/identities/{uid}/templates" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "http://localhost:8080/v2/identities/{uid}/templates",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/identities/{uid}/templates", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Face Attributes

Find faces, judge their quality, and estimate gender and expression.

Detect Faces: Quality, Gender And Expression

POST/v2/detect

Scope verify. Attributes also need the analytics licence module.

Body

FieldTypeDescription
attributesstringComma-separated subset of gender,expression to compute. Default: the attributes enabled in the tenant policy. Asking for one that is not enabled is 403 FEATURE_DISABLED.
imagerequiredstringThe image (the first image field is used). Multipart: an image file part (JPEG, PNG, BMP or WebP). JSON: the image as base64, optionally as a data: URL.
qualityenumQuality policy the passes / reasons of each face are judged against. default, surveillance, enrol, strict_enrol, passport, cctv

Responses

StatusMeaning
200The faces found, largest first
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
413Payload too large
429Rate or licence limit reached
503Busy, retry shortly

Request

curl -s -X POST "http://localhost:8080/v2/detect" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "http://localhost:8080/v2/detect",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/detect", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "face_count": 1,
  "faces": [
    {
      "bbox": {
        "height": 143,
        "width": 102,
        "x": 81,
        "y": 50
      },
      "confidence": 0.9999,
      "landmarks": [
        [
          104,
          112
        ],
        [
          149,
          112
        ],
        [
          124,
          136
        ],
        [
          103,
          155
        ],
        [
          152,
          156
        ]
      ],
      "passes": true,
      "quality": {
        "brightness": 150.2,
        "eye_distance": 45,
        "face_px": 143,
        "pitch": 8.6,
        "roll": 0,
        "sharpness": 111.8,
        "yaw": -5.1
      },
      "reasons": []
    }
  ]
}

Liveness

Passive liveness from photos and active (guided) liveness sessions.

Passive Liveness (1-5 Photos)

POST/v2/liveness

Scope liveness, licence module passive_liveness. Fails closed: any problem with the frames gives inconclusive, never live. Covers printed photos and screen replays.

Body

FieldTypeDescription
imagerequiredstringA frame; send 1 to 5 frames of the same person (5 recommended), the first 5 are used. Multipart: an image file part (JPEG, PNG, BMP or WebP). JSON: the image as base64, optionally as a data: URL.
profileenumDecision threshold profile. convenience, security

Responses

StatusMeaning
200The decision
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
413Payload too large
429Rate or licence limit reached
503Busy, retry shortly

Request

curl -s -X POST "http://localhost:8080/v2/liveness" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "http://localhost:8080/v2/liveness",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/liveness", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Result-signing Public Key

GET/v2/liveness/public-key

Verify signature over the UTF-8 bytes of signed_payload with this key.

Responses

StatusMeaning
200The instance's key
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X GET "http://localhost:8080/v2/liveness/public-key"
import requests

r = requests.get(
    "http://localhost:8080/v2/liveness/public-key",
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/liveness/public-key", {
  method: "GET",
});
console.log(r.status, await r.text());

Start An Active Liveness Session

POST/v2/liveness/sessions

Scope liveness, licence module active_liveness. Spends one call of the licence budget. The body is optional. Connect to ws_url within ttl_seconds.

Body

FieldTypeDescription
flash_opt_outbooleanSkip the colour-flash step (e.g. for photosensitive users).
profileenumstandard, high, accessible

Responses

StatusMeaning
200The session plan
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
429Rate or licence limit reached
503Busy, retry shortly

Request

curl -s -X POST "http://localhost:8080/v2/liveness/sessions" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "http://localhost:8080/v2/liveness/sessions",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/liveness/sessions", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

State And Signed Result Of A Session

GET/v2/liveness/sessions/{id}

Scope liveness, same tenant as the session. Results stay available for 10 minutes after creation.

Parameters

FieldTypeDescription
idrequired pathstringSession id.

Responses

StatusMeaning
200The state; when done, the signed result
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X GET "http://localhost:8080/v2/liveness/sessions/{id}" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/liveness/sessions/{id}",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/liveness/sessions/{id}", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Active Liveness Stream (WebSocket)

GET/v2/liveness/sessions/{id}/stream

WebSocket, protocol version 1. No API key: the session id is the credential, usable once within 60 s of creation (a second connect is 409, an unknown or expired id 404). Client messages: binary [u32 seq BE][f64 capture time ms BE][JPEG], or text {"type":"frame","seq","ts","image":"<base64 JPEG>"}, or {"type":"cancel"}. Frames: un-mirrored, at least 640x480, 10-15 fps (15 during the colour flash), seq increasing, at most 2 MiB. Server messages (text JSON): ready {session_id, profile, steps, protocol_version}; progress {phase: align|neutral|step|flash, step, of, hint}; instruction {step, of, challenge, text, deadline_ms}; flash {index, of, name, colour, rgb, duration_ms} (fill the screen with this colour until the next flash or flash_end); flash_end; step_result {step, of, challenge, passed, reason, ms}; finally result (an ActiveLivenessEnvelope with type: result) and the socket closes. Fails closed: a protocol error, disconnect or timeout gives passed: false with a reason.

Parameters

FieldTypeDescription
idrequired pathstringSession id.

Responses

StatusMeaning
101Switching to the WebSocket protocol
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found
409Conflict

Request

curl -s -X GET "http://localhost:8080/v2/liveness/sessions/{id}/stream"
import requests

r = requests.get(
    "http://localhost:8080/v2/liveness/sessions/{id}/stream",
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/liveness/sessions/{id}/stream", {
  method: "GET",
});
console.log(r.status, await r.text());

Cameras, Video And Events

Add-on: RTSP cameras, the webcam stream, recognition events, webhooks and live video.

List Cameras

GET/v2/cameras

Scope streams.

Responses

StatusMeaning
200The tenant's cameras
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X GET "http://localhost:8080/v2/cameras" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/cameras",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/cameras", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Add An RTSP Camera

POST/v2/cameras

Scope streams, licence module streaming. An enabled camera starts at once; when it cannot, running is false and note says why. 429 LICENSE_LIMIT at the licence's camera limit.

Body

FieldTypeDescription
cooldown_snumberSeconds between events for the same identity on this camera.
enabledboolean
fpsnumberAnalysed frames per second (default 10).
min_face_pxintegerSmallest face analysed (default 32).
namerequiredstring
urlrequiredstringrtsp:// or rtsps:// URL, credentials included (stored sealed, never returned).
zonesany[]Detection zones (polygons in normalised frame coordinates 0-1, a detection is kept when its centre is inside); default none.

Responses

StatusMeaning
201The camera
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
429Rate or licence limit reached

Request

curl -s -X POST "http://localhost:8080/v2/cameras" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "http://localhost:8080/v2/cameras",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/cameras", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Get A Camera

GET/v2/cameras/{id}

Scope streams.

Parameters

FieldTypeDescription
idrequired pathstringCamera id.

Responses

StatusMeaning
200The camera
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X GET "http://localhost:8080/v2/cameras/{id}" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/cameras/{id}",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/cameras/{id}", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Delete A Camera

DELETE/v2/cameras/{id}

Scope streams.

Parameters

FieldTypeDescription
idrequired pathstringCamera id.

Responses

StatusMeaning
204Deleted (its stream stopped)
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X DELETE "http://localhost:8080/v2/cameras/{id}" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.delete(
    "http://localhost:8080/v2/cameras/{id}",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/cameras/{id}", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Latest Camera Frame

GET/v2/cameras/{id}/snapshot

Scope streams. Only when the tenant policy has camera_snapshots on (403 FEATURE_DISABLED otherwise); 404 when the camera is not running or has no frame yet. With purpose=zones (scope admin), one frame for the zone editor is allowed even with pictures off; that is written to the audit log (camera.frame_for_zones).

Parameters

FieldTypeDescription
idrequired pathstringCamera id.
purpose queryenumzones: a frame for the zone editor. zones

Responses

StatusMeaning
200The latest analysed frame, at most 640 px wide, without overlays
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X GET "http://localhost:8080/v2/cameras/{id}/snapshot" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/cameras/{id}/snapshot",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/cameras/{id}/snapshot", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Status Of A Camera Or Webcam Stream

GET/v2/cameras/{id}/status

Scope streams. id: a camera id or a webcam stream's camera_id. A running stream answers its metrics; a stored camera that is not running answers its state.

Parameters

FieldTypeDescription
idrequired pathstringCamera id.

Responses

StatusMeaning
200Metrics of the stream
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X GET "http://localhost:8080/v2/cameras/{id}/status" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/cameras/{id}/status",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/cameras/{id}/status", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Live Boxes Of A Camera (WebSocket)

GET/v2/cameras/{id}/tracks

WebSocket. Scope streams, licence module streaming. Live box data of one camera, for drawing boxes and names over its video: one {"type":"tracks", camera_id, seq, ts, width, height, tracks:[{track_id, box:[x1,y1,x2,y2], state: tracking|identified|unknown, uid?, name? (display name), similarity?, groups?:[{name, level}], alert?: alert|notice, attributes?: {gender?, expression?}}]} per analysed frame (at most the camera's fps). box is normalised to the frame (0..1); ts is the frame's capture time (Unix ms); width / height are the analysed frame's size. While the camera is not running the server sends {"type":"idle", camera_id} once and keeps the socket open; boxes resume when the camera starts again. Client messages are ignored. 404 for an unknown camera.

Parameters

FieldTypeDescription
key querystringAPI key, for browsers that cannot set headers on a WebSocket (percent-encoded). Ignored when an Authorization, x-api-key or x-key-secret header is present.
idrequired pathstringCamera id.

Responses

StatusMeaning
101Switching to the WebSocket protocol
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X GET "http://localhost:8080/v2/cameras/{id}/tracks" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/cameras/{id}/tracks",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/cameras/{id}/tracks", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Watch A Camera's Live Video (WebRTC, WHEP)

POST/v2/cameras/{id}/whep

WHEP (RFC 9725): send the browser's WebRTC offer (receive-only video) as application/sdp. Scope streams, licence module streaming; the tenant must allow camera pictures (camera_snapshots). The camera's video starts on the first viewer (720p, at most 15 fps, face boxes and zone outlines drawn in, no names) and stops 30 s after the last viewer left; recognition is not affected. Names and scores come from /v2/cameras/{id}/tracks and are drawn by the client. While the camera sends no picture the video shows a "Camera disconnected" frame. 409 when the camera is not running.

Parameters

FieldTypeDescription
idrequired pathstringCamera id.

Responses

StatusMeaning
201The SDP answer. Media then flows from the media server over UDP (port 8000 of the server's public address). Location names this viewer's session (end it with DELETE).
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
409Conflict
503Busy, retry shortly

Request

curl -s -X POST "http://localhost:8080/v2/cameras/{id}/whep" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "http://localhost:8080/v2/cameras/{id}/whep",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/cameras/{id}/whep", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

End A Live Video Session

DELETE/v2/cameras/{id}/whep/{session}

Scope streams. Ends the viewer session named by the Location of the WHEP answer (closing the peer connection has the same effect after a few seconds).

Parameters

FieldTypeDescription
idrequired pathstringCamera id.
sessionrequired pathstringThe session id from Location.

Responses

StatusMeaning
200Ended
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X DELETE "http://localhost:8080/v2/cameras/{id}/whep/{session}" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.delete(
    "http://localhost:8080/v2/cameras/{id}/whep/{session}",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/cameras/{id}/whep/{session}", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Replace A Camera's Zones

PUT/v2/cameras/{id}/zones

Scopes streams and admin. Zones are regions of interest: polygons in normalised frame coordinates (0-1); a face is processed when the centre of its box is inside any zone; no zones = the whole frame. At most 16 zones of 3-64 points. Audited (camera.zones, zone counts before and after).

Parameters

FieldTypeDescription
idrequired pathstringCamera id.

Body

FieldTypeDescription
zonesrequiredobject[]
zones[].namestring
zones[].pointsrequirednumber[][]

Responses

StatusMeaning
200The camera, with its new zones (its stream restarted)
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X PUT "http://localhost:8080/v2/cameras/{id}/zones" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.put(
    "http://localhost:8080/v2/cameras/{id}/zones",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/cameras/{id}/zones", {
  method: "PUT",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Event Stream (WebSocket)

GET/v2/events

WebSocket. Scope streams, licence module streaming. The server sends {"type":"event","event":StreamEvent} for each recognition event of the tenant (CloudEvents 1.0 structured JSON, see the StreamEvent schema) and {"type":"lagged","dropped":n} when this client fell behind and lost events. Client messages are ignored.

Parameters

FieldTypeDescription
key querystringAPI key, for browsers that cannot set headers on a WebSocket (percent-encoded). Ignored when an Authorization, x-api-key or x-key-secret header is present.
camera_id querystringOnly this camera's events.

Responses

StatusMeaning
101Switching to the WebSocket protocol
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X GET "http://localhost:8080/v2/events" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/events",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/events", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Stored Recognition Events

GET/v2/events/history

Scope streams. Every published event is stored and kept for the tenant's event_retention_days.

Parameters

FieldTypeDescription
camera_id querystringOnly this camera.
uid querystringOnly this identity.
before_id queryintegerPaging: next_before_id of the previous page.
limit queryintegerPage size.
since queryintegerAt or after this time (Unix ms).
until queryintegerBefore this time (Unix ms).

Responses

StatusMeaning
200Events, newest first
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X GET "http://localhost:8080/v2/events/history" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/events/history",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/events/history", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Running Streams

GET/v2/streams

Scope streams.

Responses

StatusMeaning
200Every running stream of the tenant
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X GET "http://localhost:8080/v2/streams" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/streams",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/streams", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Webcam Stream (WebSocket)

GET/v2/webcam

WebSocket. Scope streams, licence module streaming; the Demo edition allows one stream. The client sends binary messages, each a JPEG frame (at most 4 MiB), optionally prefixed by an 8-byte little-endian Unix-ms capture timestamp; and text messages {"type":"status"} (answered with {"type":"status","status":StreamStatus}) or {"type":"end"} (finish: open tracks are decided, their events delivered, then {"type":"ended","status":StreamStatus} and the socket closes). The server sends {"type":"ready","camera_id"} first, then one {"type":"overlay", seq, ts, width, height, latency_ms, tracks:[{track_id, bbox:[x1,y1,x2,y2], box:[x1,y1,x2,y2] (0..1), face?, state: tracking|identified|unknown, uid?, similarity?, groups?, alert?}]} per analysed frame, {"type":"event","event":StreamEvent} for this camera's events and {"type":"error", code, detail} for a refused frame. 409 when a stream with this camera id is running, 429 at the stream limit.

Parameters

FieldTypeDescription
key querystringAPI key, for browsers that cannot set headers on a WebSocket (percent-encoded). Ignored when an Authorization, x-api-key or x-key-secret header is present.
camera_id querystringCamera id used in events and status: 1-64 characters of A-Z a-z 0-9 - _ . (default webcam).
fps querynumberAnalysed frames per second (1-15, default 10).

Responses

StatusMeaning
101Switching to the WebSocket protocol
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
409Conflict
429Rate or licence limit reached

Request

curl -s -X GET "http://localhost:8080/v2/webcam" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/webcam",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/webcam", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

List Webhooks

GET/v2/webhooks

Scope admin.

Responses

StatusMeaning
200The tenant's webhooks
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X GET "http://localhost:8080/v2/webhooks" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/webhooks",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/webhooks", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Add A Webhook

POST/v2/webhooks

Scope admin, licence module streaming. Each event is POSTed as application/cloudevents+json (a StreamEvent) with headers x-nf-signature: sha256=<hex HMAC-SHA256(secret, body)>, x-nf-event-id and x-nf-webhook-id; failed deliveries are retried with exponential backoff.

Body

FieldTypeDescription
eventsenum[]Event types to deliver; empty: all.
secretstringSigning secret; generated when absent.
urlrequiredstringhttp:// or https:// URL (at most 2048 characters).

Responses

StatusMeaning
201The webhook
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X POST "http://localhost:8080/v2/webhooks" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "http://localhost:8080/v2/webhooks",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/webhooks", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Delete A Webhook

DELETE/v2/webhooks/{id}

Scope admin.

Parameters

FieldTypeDescription
idrequired pathstringWebhook id.

Responses

StatusMeaning
204Deleted
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X DELETE "http://localhost:8080/v2/webhooks/{id}" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.delete(
    "http://localhost:8080/v2/webhooks/{id}",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/webhooks/{id}", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Administration

API keys, the tenant policy, request log, audit log and consent (scope admin).

Read The Audit Log

GET/v2/tenant/audit

Scope admin.

Parameters

FieldTypeDescription
after_seq queryintegerEntries after this sequence number (paging).
limit queryintegerPage size (at most 1000).
actor querystringOnly entries whose actor contains this text (an e-mail finds user:<e-mail>).
action querystringOnly entries whose action starts with this text (identity. finds every identity change).
since queryintegerOnly entries at or after this time (Unix ms).
until queryintegerOnly entries before this time (Unix ms).

Responses

StatusMeaning
200Entries in sequence order
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X GET "http://localhost:8080/v2/tenant/audit" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/tenant/audit",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/tenant/audit", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Export The Audit Log

GET/v2/tenant/audit/export

Scope admin. At most 200,000 rows.

Parameters

FieldTypeDescription
format queryenumOnly csv. csv
actor querystringOnly entries of this actor.
action querystringOnly entries of this action.
since queryintegerAt or after this time (Unix ms).
until queryintegerBefore this time (Unix ms).

Responses

StatusMeaning
200CSV: seq, time, actor, action, target, detail (JSON). No biometric data.
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X GET "http://localhost:8080/v2/tenant/audit/export" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/tenant/audit/export",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/tenant/audit/export", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Check The Audit Hash Chain

GET/v2/tenant/audit/verify

Scope admin. Walks the instance's whole chain.

Responses

StatusMeaning
200Result of the check
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X GET "http://localhost:8080/v2/tenant/audit/verify" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/tenant/audit/verify",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/tenant/audit/verify", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Record Privacy-notice Consent

POST/v2/tenant/consent

Scope admin. From the dashboard this needs an elevated session (POST /v2/auth/elevate); 403 otherwise. Stored in the audit log and in the policy's consent; needed before store_crops can be on.

Body

FieldTypeDescription
accepted_byrequiredstring
notice_versionrequiredstring

Responses

StatusMeaning
200Recorded
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X POST "http://localhost:8080/v2/tenant/consent" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "http://localhost:8080/v2/tenant/consent",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/tenant/consent", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Withdraw Consent

DELETE/v2/tenant/consent

Scope admin. From the dashboard this needs an elevated session (POST /v2/auth/elevate); 403 otherwise.

Responses

StatusMeaning
200Withdrawn; store_crops is off
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X DELETE "http://localhost:8080/v2/tenant/consent" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.delete(
    "http://localhost:8080/v2/tenant/consent",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/tenant/consent", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

List API Keys

GET/v2/tenant/keys

Scope admin.

Responses

StatusMeaning
200Every key of the tenant, revoked ones included
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X GET "http://localhost:8080/v2/tenant/keys" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/tenant/keys",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/tenant/keys", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Create An API Key

POST/v2/tenant/keys

Scope admin. From the dashboard this also needs an elevated session; 403 otherwise.

Body

FieldTypeDescription
expires_atintegerExpiry (Unix ms).
namestringLabel for the key.
scopesrequiredenum[]

Responses

StatusMeaning
200The new key (secret shown once)
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X POST "http://localhost:8080/v2/tenant/keys" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "http://localhost:8080/v2/tenant/keys",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/tenant/keys", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Revoke An API Key

DELETE/v2/tenant/keys/{id}

Scope admin. From the dashboard this also needs an elevated session; 403 otherwise.

Parameters

FieldTypeDescription
idrequired pathstringKey id.

Responses

StatusMeaning
200Revoked
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X DELETE "http://localhost:8080/v2/tenant/keys/{id}" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.delete(
    "http://localhost:8080/v2/tenant/keys/{id}",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/tenant/keys/{id}", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Get The Tenant Policy

GET/v2/tenant/policy

Scope admin.

Responses

StatusMeaning
200The policy
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X GET "http://localhost:8080/v2/tenant/policy" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/tenant/policy",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/tenant/policy", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Change The Tenant Policy

PUT/v2/tenant/policy

Scope admin. Send only the members to change; members left out keep their current value (objects such as attributes are merged member by member; PATCH does the same). verify_far must be in [1e-7, 0.1], identify_fpir in [1e-4, 0.1], verify_profile access or access-relaxed, event_retention_days and request_retention_days in [1, 3650], audit_retention_days in [30, 3650]. store_crops can only be on while a consent record exists; consent is ignored here (POST /v2/tenant/consent). From the dashboard, changing retention, store_crops or camera_snapshots needs an elevated session.

Body

FieldTypeDescription
attributesobject
attributes.expressionboolean
attributes.genderboolean
audit_retention_daysintegerThe audit chain is shared by the instance: the longest value of all tenants applies.
camera_snapshotsbooleanAllow GET /v2/cameras/{id}/snapshot.
consentone ofRead-only here; see POST /v2/tenant/consent.
enrolenumEnrolment strictness. cctv: for CCTV snapshots, lower accuracy, prefer photos. standard, passport, cctv
event_retention_daysinteger
identify_fpirnumber
request_retention_daysintegerRequest-log rows are deleted after this many days.
store_cropsbooleanKeep aligned enrolment crops; only while a consent record exists.
verify_farnumber
verify_profileenumaccess, access-relaxed

Responses

StatusMeaning
200The stored policy
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X PUT "http://localhost:8080/v2/tenant/policy" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.put(
    "http://localhost:8080/v2/tenant/policy",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/tenant/policy", {
  method: "PUT",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Change Some Members Of The Tenant Policy

PATCH/v2/tenant/policy

Scope admin. Send only the members to change; members left out keep their current value (objects such as attributes are merged member by member; PATCH does the same). verify_far must be in [1e-7, 0.1], identify_fpir in [1e-4, 0.1], verify_profile access or access-relaxed, event_retention_days and request_retention_days in [1, 3650], audit_retention_days in [30, 3650]. store_crops can only be on while a consent record exists; consent is ignored here (POST /v2/tenant/consent). From the dashboard, changing retention, store_crops or camera_snapshots needs an elevated session.

Body

FieldTypeDescription
attributesobject
attributes.expressionboolean
attributes.genderboolean
audit_retention_daysintegerThe audit chain is shared by the instance: the longest value of all tenants applies.
camera_snapshotsbooleanAllow GET /v2/cameras/{id}/snapshot.
consentone ofRead-only here; see POST /v2/tenant/consent.
enrolenumEnrolment strictness. cctv: for CCTV snapshots, lower accuracy, prefer photos. standard, passport, cctv
event_retention_daysinteger
identify_fpirnumber
request_retention_daysintegerRequest-log rows are deleted after this many days.
store_cropsbooleanKeep aligned enrolment crops; only while a consent record exists.
verify_farnumber
verify_profileenumaccess, access-relaxed

Responses

StatusMeaning
200The stored policy
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X PATCH "http://localhost:8080/v2/tenant/policy" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.patch(
    "http://localhost:8080/v2/tenant/policy",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/tenant/policy", {
  method: "PATCH",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Read The Request Log

GET/v2/tenant/requests

Scope admin. One row per API call: time, method, path, status, latency, the acting key prefix or user, client IP, the endpoint family and the error code; detail holds the request's shape (field names, photo type and size, settings such as model or far; other fields only as given) and an outcome line without people's IDs. Request and response bodies, images and templates are never recorded. The dashboard's own background status reads are left out. Kept for the tenant's request_retention_days.

Parameters

FieldTypeDescription
since queryintegerCalls at or after this time (Unix ms).
until queryintegerCalls before this time (Unix ms).
method querystringOnly this HTTP method.
path querystringOnly paths starting with this text.
status queryintegerStatus class: 2 = 2xx, 4 = 4xx, 5 = 5xx.
actor querystringOnly calls whose actor contains this text.
family queryenumOnly this endpoint family. compare, verify, identify, detect, liveness, enrol, people, video, admin, service, dashboard
before_id queryintegerRows older than this row id (paging).
limit queryintegerPage size (at most 500).

Responses

StatusMeaning
200API calls, newest first
400Bad request
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)

Request

curl -s -X GET "http://localhost:8080/v2/tenant/requests" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/tenant/requests",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/tenant/requests", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Read One Logged Call

GET/v2/tenant/requests/{id}

Scope admin. One row of the request log with its detail.

Parameters

FieldTypeDescription
idrequired pathstringRow id.

Responses

StatusMeaning
200The call
401Not authenticated
403Not allowed (scope, read-only account, elevation, edition or licence module)
404Not found

Request

curl -s -X GET "http://localhost:8080/v2/tenant/requests/{id}" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "http://localhost:8080/v2/tenant/requests/{id}",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/tenant/requests/{id}", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Service

Version, models and their operating points, licence, this description.

Product, Version And Edition

GET/v2/info

Responses

StatusMeaning
200Service information

Request

curl -s -X GET "http://localhost:8080/v2/info"
import requests

r = requests.get(
    "http://localhost:8080/v2/info",
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/info", {
  method: "GET",
});
console.log(r.status, await r.text());

Licence Status

GET/v2/licence

Also served in activation mode. A development build without a licence check answers {state: development, note}.

Responses

StatusMeaning
200The licence summary

Request

curl -s -X GET "http://localhost:8080/v2/licence"
import requests

r = requests.get(
    "http://localhost:8080/v2/licence",
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/licence", {
  method: "GET",
});
console.log(r.status, await r.text());

Activate Online

POST/v2/licence/activate

Activation mode only (before the licence is active); needs the x-setup-token header printed at start-up. Errors are {status, detail} JSON documents, not problem documents. Activates with the licensing server.

Parameters

FieldTypeDescription
x-setup-tokenrequired headerstringOne-off setup token printed at start-up while the installation is not activated.

Body

FieldTypeDescription
keyrequiredstringThe software licence key.

Responses

StatusMeaning
200Activated; the server restarts into normal operation
400key missing
401Missing or wrong setup token
409Accepted but the licence does not allow analysis
502The licensing server could not be reached or refused

Request

curl -s -X POST "http://localhost:8080/v2/licence/activate" \
  -H "x-setup-token: <X_SETUP_TOKEN>"
import requests

r = requests.post(
    "http://localhost:8080/v2/licence/activate",
    headers={"x-setup-token": "<X_SETUP_TOKEN>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/licence/activate", {
  method: "POST",
  headers: { "x-setup-token": "<X_SETUP_TOKEN>" },
});
console.log(r.status, await r.text());

Import An Offline Licence File

POST/v2/licence/offline-import

Activation mode only (before the licence is active); needs the x-setup-token header printed at start-up. Errors are {status, detail} JSON documents, not problem documents. The body is the licence file as text.

Parameters

FieldTypeDescription
x-setup-tokenrequired headerstringOne-off setup token printed at start-up while the installation is not activated.

Responses

StatusMeaning
200Imported and activated
400Invalid licence file
401Missing or wrong setup token
409Accepted but the licence does not allow analysis

Request

curl -s -X POST "http://localhost:8080/v2/licence/offline-import" \
  -H "x-setup-token: <X_SETUP_TOKEN>"
import requests

r = requests.post(
    "http://localhost:8080/v2/licence/offline-import",
    headers={"x-setup-token": "<X_SETUP_TOKEN>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/licence/offline-import", {
  method: "POST",
  headers: { "x-setup-token": "<X_SETUP_TOKEN>" },
});
console.log(r.status, await r.text());

Create An Offline Activation Request

POST/v2/licence/offline-request

Activation mode only (before the licence is active); needs the x-setup-token header printed at start-up. Errors are {status, detail} JSON documents, not problem documents. For machines without access to the licensing server.

Parameters

FieldTypeDescription
x-setup-tokenrequired headerstringOne-off setup token printed at start-up while the installation is not activated.

Body

FieldTypeDescription
keyrequiredstringThe software licence key.

Responses

StatusMeaning
200The offline activation request, to take to the licensing portal
400key missing
401Missing or wrong setup token
403The request could not be created for this key

Request

curl -s -X POST "http://localhost:8080/v2/licence/offline-request" \
  -H "x-setup-token: <X_SETUP_TOKEN>"
import requests

r = requests.post(
    "http://localhost:8080/v2/licence/offline-request",
    headers={"x-setup-token": "<X_SETUP_TOKEN>"},
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/licence/offline-request", {
  method: "POST",
  headers: { "x-setup-token": "<X_SETUP_TOKEN>" },
});
console.log(r.status, await r.text());

Embedders And Calibrated Operating Points

GET/v2/models

Responses

StatusMeaning
200The loaded embedders and their calibration

Request

curl -s -X GET "http://localhost:8080/v2/models"
import requests

r = requests.get(
    "http://localhost:8080/v2/models",
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/models", {
  method: "GET",
});
console.log(r.status, await r.text());

This API Description

GET/v2/openapi.json

Responses

StatusMeaning
200OpenAPI 3.1 document for this edition

Request

curl -s -X GET "http://localhost:8080/v2/openapi.json"
import requests

r = requests.get(
    "http://localhost:8080/v2/openapi.json",
)
print(r.status_code, r.text)
const r = await fetch("http://localhost:8080/v2/openapi.json", {
  method: "GET",
});
console.log(r.status, await r.text());
Was this page helpful?