TAA Vision. / Docs / Manual

User Manual

TAA Vision

A standalone, cloud-agnostic computer‑vision platform with three capabilities — object detection, detail extraction (plates, text, OCR), and identity (facial recognition). Any product consumes it over a REST API plus edge apps. This manual covers every part, from your first API call to running the fleet.

Overview #

TAA Vision turns camera frames into structured answers to three questions: what is in this photo, what does it say, and who is this. It is multi-tenant, provider-agnostic, and runs the same code in the cloud or on-prem.

Object detection
“What’s in this photo?” — people, vehicles, objects with bounding boxes and confidence.
Detail extraction
License plates, boat registrations, text and OCR pulled out of a frame or crop.
Identity
Facial recognition: 1:N identification against an enrolled group, and 1:1 verification.

You interact with the platform three ways: direct API calls with an API key, the edge apps (a mobile capture app and a headless processor) that stream results to you, and the admin console (this site) for managing tenants, keys, devices, and events.

Every request is scoped to a tenant. Faces, plates, events, and rate limits are isolated per tenant — an API key never sees another tenant’s data.

Architecture #

Four cooperating parts, all speaking the same REST + MQTT contract.

PartStackRole
API serverNode 22 · Express · TypeScriptThe REST API and event pipeline. SQLite locally, Postgres + pgvector in the cloud.
Admin consoleAstro · Vue islandsThis dashboard — tenants, API keys, edge devices, activity, billing, and these docs.
Edge (mobile)Capacitor · face-api.jsA phone/tablet capture app that runs detection in-browser and reports over the API bridge.
Edge (processor)Python · InsightFace · DockerA headless detector for Linux x86/ARM and Jetson that pulls RTSP/USB streams and runs identify locally.

Cloud calls go through provider interfaces (IFaceProvider, IVisionProvider, IStorageProvider) so the same server runs on AWS Rekognition, Azure, or the on-prem processor by flipping an env var. Storage and the data store are abstracted the same way.

The edge processor gives you a structural cost advantage: scans handled locally never hit a paid cloud vision API, which matters most during event-weekend surges.

Authentication & API keys #

Programmatic access uses per-tenant API keys; the console uses an admin session.

Every API key is prefixed tvk_ and carries a set of scopes. Send it on every request in the X-API-Key header:

# Every API request carries your tenant key
curl https://api.vision.xos.taa.io/api/v1/identity/identify \
  -H "X-API-Key: tvk_live_9f2c…" \
  -H "Content-Type: application/json" \
  -d '{ "faceGroupId": "guests", "imageBase64": "…" }'

Keys are minted per tenant in the console (API Keys) or via the admin API. Available scopes:

  • detect, extract, identity — the three scan families.
  • faces, enroll, unenroll — manage face groups and enrollments.
  • plates — manage plate watchlists and run plate scans.
  • events — read and write the tenant event log.

A key is shown once at creation — store it immediately. Revoking a key is instant and breaks any edge device still using it, so generate a replacement config first. A key belonging to a trashed tenant stops authenticating even before it’s revoked.

The admin console authenticates people (not machines) with an email/password login that returns a JWT. Admin-only actions — creating tenants, minting keys, managing devices — require an admin role session, not an API key.

Enrollment #

Identity works against face groups — named collections of enrolled subjects. Enroll before you identify.

  1. Create a groupPOST /api/v1/faces/groups with a name (e.g. guests). Groups are per-tenant.
  2. Enroll subjectsPOST /api/v1/faces/enroll with a base64 image and your own externalId (an employee id, member number, anything stable).
  3. Identify — scans against the group return the matched externalId and a confidence score.
POST /api/v1/faces/enroll
# X-API-Key: tvk_… (scope: enroll)
{
  "faceGroupId": "guests",
  "imageBase64": "/9j/4AAQSk…",
  "externalId": "MEMBER-4471"
}

Enrollment photos (only enrollment photos — never scan images) are archived to object storage keyed on the enrollment, so identities survive a provider switch. Delete a single enrollment with DELETE /api/v1/faces/enrollments/:id, or purge everything for a subject across groups with DELETE /api/v1/faces/subjects/:externalId.

Biometric consent is captured at the app + privacy-policy layer before enrollment. The service-side consent check is a backstop, not the primary gate.

Identity — identify & verify #

Two questions, two endpoints.

Identify (1:N)
POST /api/v1/identity/identify — “who is this?” Searches a face group and returns the best match + confidence, or nothing.
Verify (1:1)
POST /api/v1/identity/verify — “is this the person I think it is?” Compares a probe image against one enrolled externalId.
POST /api/v1/identity/identify
{
  "faceGroupId": "guests",
  "imageBase64": "/9j/4AAQSk…"
}
→ { "match": true, "externalId": "MEMBER-4471", "confidence": 98.4 }

Scans are ephemeral by design — the probe image is not persisted. Each scan writes an audit event to the tenant log (matched or unmatched) and, when a broker is configured, publishes to MQTT in real time (see Events & MQTT).

Detection & extraction #

Non-identity vision: what’s in the frame, and what does it say.

  • DetectPOST /api/v1/detect returns labelled objects (people, vehicles, …) with bounding boxes and confidence.
  • ExtractPOST /api/v1/extract pulls text / OCR and structured details out of a frame or crop.
POST /api/v1/detect
# X-API-Key: tvk_… (scope: detect)
{ "imageBase64": "/9j/4AAQSk…" }
→ { "objects": [ { "label": "person", "confidence": 96.1, "box": {…} } ] }

Both run through the same provider abstraction as identity, so they honour your VISION_PROVIDER selection (AWS, Azure, or the on-prem processor).

License plates & watchlists #

Plate reading pairs OCR with per-tenant watchlists so a match can trigger an action.

  1. Create a watchlistPOST /api/v1/plates/watchlists.
  2. Add entriesPOST /api/v1/plates/watchlists/:id/entries, each a plate + your own externalId. Matching is fuzzy (normalizes O/0, I/1, spacing).
  3. ScanPOST /api/v1/plates/scan reads a plate from an image and checks it against the watchlist, emitting a match/unmatch event.

Enroll plates directly with POST /api/v1/plates/enroll, remove a single entry with DELETE /api/v1/plates/entries/:id, or clear a subject everywhere with DELETE /api/v1/plates/subjects/:externalId.

Edge devices #

Two edge apps put cameras in the field. Both self-register, report a heartbeat, and stream results to your MQTT broker.

Mobile app
A Capacitor (iOS + Android) app wrapping the console’s capture pages. Runs face detection in-browser via face-api.js; a JS bridge routes fetch/WebSocket to the central API — no local server needed.
Headless processor
A Dockerized Python detector for Linux x86/ARM and Jetson. Pulls USB / RTSP / WebRTC streams, runs InsightFace locally, and can serve identify offline as a cloud failover.

Provisioning a device — the console generates an edge config bundle carrying the tenant identity, a scoped API key, and MQTT settings. Deliver it two ways:

  1. Scan a QR code — open Settings → Generate Edge Config, pick a location/device id and capture mode, and the device scans the QR to configure itself.
  2. Import JSON — download the same bundle as a file and import it on the device’s setup screen.

The generated key is named edge:<location>/<device> so you can revoke a single device independently. Capture can be left as-is, set to browser camera, or a node source (USB / RTSP / WebRTC) with an FPS and downscale (CAPTURE_WIDTH) applied via the pipeline.

Treat an edge config QR like a credential — it contains an API key. Anyone who scans it can act as that device.

Events & MQTT #

Every scan writes an audit event, and — when you configure a broker — publishes to MQTT in real time so your app reacts the instant a camera sees someone.

Point us at your broker (or leave it unset to just poll the event API):

PUT /api/v1/tenants/{tenantId}/mqtt
{
  "mqttBrokerUrl": "mqtts://broker.example.com:8883",
  "mqttUsername": "my-user",
  "mqttPassword": "my-pass",
  "mqttTopicPrefix": "my-app/vision",
  "mqttClientId": "taa-vision-park1"
}

Results publish under your prefix. The core topics:

TopicWhenPayload
{prefix}/{tenantId}/events/identifyFace identify scanexternalId, confidence, deviceId, locationId, timestamp
{prefix}/{tenantId}/events/verify1:1 verifymatch result + confidence
{prefix}/{tenantId}/events/detectObject detectionlabelled objects
{prefix}/{tenantId}/events/extractPlate / text extractionplate or text + match
{prefix}/{tenantId}/edges/{edgeId}/heartbeatEdge check-inonline status, last-seen

Each API/worker task connects to your broker with a unique client id, so the platform scales horizontally without connections fighting over a shared id. A flapping broker connection now trips a CloudWatch alarm instead of failing silently.

Admin console #

This site. Everything you can do by API, plus fleet visibility.

  • Dashboard — tenants, edges online/total, MQTT + platform status, with sortable tenant tables and live edge counts.
  • Tenant detail — activity, history, edge devices (rename/tag/remove), API keys, usage & billing, and settings (external MQTT, edge config & QR).
  • Users — console accounts, roles (admin / tenant), and per-tenant access.
  • Trash — soft-deleted tenants. A trashed tenant is deactivated and hidden but fully recoverable; Restore brings it back with API keys and enrollments intact.

Deleting a tenant is a soft delete (“Move to Trash”), not a permanent wipe — the record and event history are retained. Restore it from the Trash view.

Deployment & operations #

How the cloud deployment is shaped, and how to ship a change.

  • API (api.vision.xos.taa.io) — an autoscaled Express container on ECS Fargate behind an ALB with WAF.
  • MQTT (mqtt.vision.xos.taa.io) — AWS IoT Core; edge devices connect with per-device X.509 certs scoped to their tenant.
  • Dashboard (vision.xos.taa.io) — this Astro build on S3 + CloudFront.
  • Data — shared RDS Postgres with a dedicated vision DB and pgvector; enrollment photos in S3.

Deploying: the Deploy prod (API + Web) GitHub Actions workflow is a manual “Run workflow” button — pick which stacks to deploy (all / api / web / data) and it validates, runs the CDK deploy, applies DB migrations in-VPC, and smoke-tests. Migrations are backward-compatible and run as a one-off ECS task on the RDS security group.

Alarms wired to SNS cover ALB 5xx, unhealthy hosts, p99 latency, CPU/memory, and tenant-MQTT broker flap — so a silent failure pages someone instead of hiding in the logs.

API reference #

All routes live under /api/v1. Scan/identity/plate routes need the matching scope.

MethodEndpointScopeDescription
POST/api/v1/identity/identifyidentity1:N face search against a group
POST/api/v1/identity/verifyidentity1:1 verify against one subject
POST/api/v1/detectdetectObject detection
POST/api/v1/extractextractText / detail extraction
POST/api/v1/faces/groupsfacesCreate a face group
GET/api/v1/faces/groupsfacesList face groups
POST/api/v1/faces/enrollenrollEnroll a face
DELETE/api/v1/faces/enrollments/:idunenrollDelete one enrollment
DELETE/api/v1/faces/subjects/:externalIdunenrollPurge a subject across groups
POST/api/v1/plates/watchlistsplatesCreate a plate watchlist
POST/api/v1/plates/scanplatesRead a plate + check the watchlist
POST/api/v1/plates/enrollplatesEnroll a plate
GET/api/v1/eventseventsRead the tenant event log
POST/api/v1/events/batcheventsWrite a batch of events
PUT/api/v1/tenants/:id/mqttadminConfigure the tenant’s external MQTT broker
POST/api/v1/tenants/:id/api-keysadminMint a scoped API key
POST/api/v1/tenants/:id/restoreadminRestore a trashed tenant
GET/healthz · /readyzLiveness / readiness probes

Errors are JSON: 401 UNAUTHORIZED (missing/invalid key), 403 FORBIDDEN (key lacks the scope), 404 NOT_FOUND. Each carries { error: { code, message } }.

New to the platform? Start at Authentication, then EnrollmentIdentity. For what changed recently, see the Release Notes →.