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.
| Part | Stack | Role |
|---|---|---|
| API server | Node 22 · Express · TypeScript | The REST API and event pipeline. SQLite locally, Postgres + pgvector in the cloud. |
| Admin console | Astro · Vue islands | This dashboard — tenants, API keys, edge devices, activity, billing, and these docs. |
| Edge (mobile) | Capacitor · face-api.js | A phone/tablet capture app that runs detection in-browser and reports over the API bridge. |
| Edge (processor) | Python · InsightFace · Docker | A 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.
- Create a group —
POST /api/v1/faces/groupswith a name (e.g.guests). Groups are per-tenant. - Enroll subjects —
POST /api/v1/faces/enrollwith a base64 image and your ownexternalId(an employee id, member number, anything stable). - Identify — scans against the group return the matched
externalIdand 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 enrolledexternalId.
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.
- Detect —
POST /api/v1/detectreturns labelled objects (people, vehicles, …) with bounding boxes and confidence. - Extract —
POST /api/v1/extractpulls 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.
- Create a watchlist —
POST /api/v1/plates/watchlists. - Add entries —
POST /api/v1/plates/watchlists/:id/entries, each a plate + your ownexternalId. Matching is fuzzy (normalizes O/0, I/1, spacing). - Scan —
POST /api/v1/plates/scanreads 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:
- 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.
- 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:
| Topic | When | Payload |
|---|---|---|
| {prefix}/{tenantId}/events/identify | Face identify scan | externalId, confidence, deviceId, locationId, timestamp |
| {prefix}/{tenantId}/events/verify | 1:1 verify | match result + confidence |
| {prefix}/{tenantId}/events/detect | Object detection | labelled objects |
| {prefix}/{tenantId}/events/extract | Plate / text extraction | plate or text + match |
| {prefix}/{tenantId}/edges/{edgeId}/heartbeat | Edge check-in | online 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
visionDB 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.
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| POST | /api/v1/identity/identify | identity | 1:N face search against a group |
| POST | /api/v1/identity/verify | identity | 1:1 verify against one subject |
| POST | /api/v1/detect | detect | Object detection |
| POST | /api/v1/extract | extract | Text / detail extraction |
| POST | /api/v1/faces/groups | faces | Create a face group |
| GET | /api/v1/faces/groups | faces | List face groups |
| POST | /api/v1/faces/enroll | enroll | Enroll a face |
| DELETE | /api/v1/faces/enrollments/:id | unenroll | Delete one enrollment |
| DELETE | /api/v1/faces/subjects/:externalId | unenroll | Purge a subject across groups |
| POST | /api/v1/plates/watchlists | plates | Create a plate watchlist |
| POST | /api/v1/plates/scan | plates | Read a plate + check the watchlist |
| POST | /api/v1/plates/enroll | plates | Enroll a plate |
| GET | /api/v1/events | events | Read the tenant event log |
| POST | /api/v1/events/batch | events | Write a batch of events |
| PUT | /api/v1/tenants/:id/mqtt | admin | Configure the tenant’s external MQTT broker |
| POST | /api/v1/tenants/:id/api-keys | admin | Mint a scoped API key |
| POST | /api/v1/tenants/:id/restore | admin | Restore a trashed tenant |
| GET | /healthz · /readyz | — | Liveness / 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 Enrollment → Identity. For what changed recently, see the Release Notes →.