Skip to content
WITNESS · Physical security sensing

Connect the cameras you already have.

The most common buyer question after the demo is "how does it talk to my existing IP cameras?". Short answer: RTSP, ONVIF or SCS-1 directly off the wire — no proprietary SDK, no on-prem appliance, no rip-and-replace. The longer answer is below, sectioned by endpoint, with sample request/response, rate limits and the named edge region for each call.

This is a public spec, not the OpenAPI source-of-truth — that lives in the customer portal post-pilot. The shape is stable; the field names are stable. Want the OpenAPI YAML on the call? Ask at /book.

RTSP, ONVIF, SCS-1. No proprietary SDK lock-in.

We tap the same stream your Milestone, Genetec or Hikvision NVR is already reading — read-only, no replacement. The edge node authenticates as a regular RTSP client per camera. The customer gives us one read-only username per estate; we never touch the recorder.

  • RTSP

    PRIMARY

    RTSP/1.0 over TCP (or RTP over UDP where the LAN allows)

    Most BTR, hotel and venue estates speak RTSP natively. We support H.264 main/high and H.265 main, 720p–4K, up to 30fps ingestion (the model samples at 6fps — higher source rates are throttled at the edge).

    Setup: 4 lines of config per camera + one firewall rule for the LHR edge IP.

  • ONVIF

    DISCOVERY

    ONVIF Profile S + Profile T

    For estates that prefer not to hand us individual RTSP URLs, we speak ONVIF Profile S (streaming) and Profile T (advanced streaming + alerts) to a customer-side ONVIF gateway. The gateway sees Witness as a regular Profile S client; no special permissions needed.

    Setup: customer points the gateway at our ONVIF endpoint; auto-discovery handles the rest.

  • SCS-1

    GOVERNMENT

    Surveillance Camera Code — Standard 1

    Required by some local-authority CCTV control rooms and JCNSS-scoped transport-hub estates. SCS-1 mandates a specific authentication + audit-log shape; we ship a dedicated SCS-1 ingest path that emits the required audit-log entries to the customer's existing audit sink.

    Setup: the SCS-1 path is opt-in per site, configured on the call.

Bearer token, rotated quarterly, bound to ICO ZA-registration.

Every API call carries an Authorization: Bearer <token> header. Tokens are scoped per environment (pilot / production) and per site, never global. Quarterly rotation is mandatory — the previous token continues to work for a 72-hour overlap window so deploys aren't rushed. Every issued token is bound at creation to the customer's ICO ZA-registration; if either the customer's or our own ZA lapses, the token is revoked at the next rotation rather than left dangling.

  • ZA

    Our ICO ZA-registration: ZA-pending In progress at the time of this spec. Tokens issued pre-registration carry a 30-day grace period after the ZA issues, during which we re-bind every active token.

  • ROTATE

    Tokens rotate at the start of every UK calendar quarter (1 Jan, 1 Apr, 1 Jul, 1 Oct). Customers receive the new token 7 days ahead via the same channel they configured for webhook secrets.

POST /v1/embeddings

Compute and store the vector.

Submit a batch of detected-person frames; receive 512-dim embedding vectors with content-addressable IDs.

Rate limit
300 req/min per token · 50 frames/req max · soft cap negotiable for estate-tier customers
Edge region
Vercel lhr1 (London)
Auth
Bearer token (see above)

Sample request

{
  "site_id": "ste_btr_acme_block_a",
  "camera_id": "cam_lobby_north",
  "frames": [
    {
      "ts": "2026-06-15T14:23:01.842Z",
      "detection_id": "det_01HXT9F2K",
      "bbox": [120, 84, 380, 612],
      "image": "<base64-encoded JPEG, max 96kB>"
    }
  ]
}

Sample response

{
  "embeddings": [
    {
      "embedding_id": "emb_sha256_8c2e...a1f",
      "detection_id": "det_01HXT9F2K",
      "dim": 512,
      "stored_at": "2026-06-15T14:23:02.118Z",
      "expires_at": "2026-06-29T14:23:02.118Z"
    }
  ],
  "request_id": "req_01HXT9F30"
}

Notes

  • Embedding IDs are SHA-256 of the vector itself — identical embeddings collapse to one ID, so a person standing still for 5s doesn't bloat the store.
  • The raw frame is held in a 30s rolling RAM buffer for operator playback, never written to durable storage.
  • expires_at is always datePublished + 14 days. The store enforces hard expiry; there is no "keep it for the audit" flag.

POST /v1/matches

Search the recent embedding history.

Submit an embedding; receive a ranked list of candidate matches across the recent embedding history.

Rate limit
600 req/min per token — typically called once per fresh embedding, so the limit follows ingest cadence
Edge region
Neon Postgres EU-WEST-2 (London)
Auth
Bearer token (see above)

Sample request

{
  "embedding_id": "emb_sha256_8c2e...a1f",
  "site_scope": "ste_btr_acme_block_a",
  "window_minutes": 30,
  "threshold": 0.78,
  "limit": 10
}

Sample response

{
  "matches": [
    {
      "embedding_id": "emb_sha256_3b41...c9d",
      "camera_id": "cam_lift_lobby",
      "similarity": 0.842,
      "seen_at": "2026-06-15T14:08:44.290Z",
      "dwell_seconds": 11
    },
    {
      "embedding_id": "emb_sha256_07ad...e2b",
      "camera_id": "cam_landing_3f",
      "similarity": 0.811,
      "seen_at": "2026-06-15T14:14:09.660Z",
      "dwell_seconds": 4
    }
  ],
  "request_id": "req_01HXT9F31"
}

Notes

  • Cosine similarity, range 0.0–1.0. Operators tune the threshold per site — 0.78 is the v9 default that hits a reasonable precision/recall on the BTR cohort.
  • dwell_seconds is the duration the same embedding was reported on that camera before the next detection ID — a useful proxy for tail-gating and pause-at-door signals.
  • Results are ordered by similarity DESC, then by seen_at ASC (the older sighting wins ties — operators want the earliest evidence of the cross-camera trace).

POST <customer-configured URL>

Deliver real-time match events.

Outbound delivery of real-time match events crossing the configured similarity threshold.

Rate limit
Outbound from Witness — bounded by ingest cadence. Retries: 5 attempts, exponential back-off 2s/8s/32s/2m/8m
Edge region
Neon Postgres EU-WEST-2 (London)
Auth
Bearer token (see above)

Sample request

POST /your-webhook HTTP/1.1
Host: customer.example.co.uk
Content-Type: application/json
X-Witness-Signature: t=1718459789,v1=4f8a...c2e
X-Witness-Delivery: dlv_01HXT9F40

{
  "event": "match.crossed_threshold",
  "site_id": "ste_btr_acme_block_a",
  "embedding_a": "emb_sha256_8c2e...a1f",
  "embedding_b": "emb_sha256_3b41...c9d",
  "camera_a": "cam_lobby_north",
  "camera_b": "cam_lift_lobby",
  "similarity": 0.842,
  "seen_at_a": "2026-06-15T14:23:01.842Z",
  "seen_at_b": "2026-06-15T14:08:44.290Z",
  "ts": "2026-06-15T14:23:02.231Z"
}

Sample response

HTTP/1.1 200 OK
Content-Length: 0

(Any 2xx response acknowledges delivery. Any 4xx other than 408/429
permanently fails the delivery — no retry. 5xx and 408/429 trigger the
5-attempt back-off.)

Notes

  • Verify X-Witness-Signature using the per-webhook secret: HMAC-SHA256(secret, `${t}.${raw_body}`). The signed timestamp guards against replay; reject deliveries where |now − t| > 300s.
  • Idempotency: every delivery carries an X-Witness-Delivery header. Use it as the dedupe key — retries reuse the same value.
  • Customers configure the threshold per camera or per site. Below-threshold matches are silently dropped; the webhook never fires for them.

DELETE /v1/embeddings/{embedding_id}

Right-to-erasure on demand.

Right-to-erasure for an individual embedding. Bulk variant for batch retention enforcement.

Rate limit
60 req/min per token (individual) · 1 req/min per token (bulk; capped at 10,000 IDs/req)
Edge region
Neon Postgres EU-WEST-2 (London)
Auth
Bearer token (see above)

Sample request

DELETE /v1/embeddings/emb_sha256_8c2e...a1f HTTP/1.1
Authorization: Bearer <token>

—— OR (bulk) ——

POST /v1/embeddings/bulk-delete HTTP/1.1
Authorization: Bearer <token>
Content-Type: application/json

{
  "embedding_ids": [
    "emb_sha256_8c2e...a1f",
    "emb_sha256_3b41...c9d"
  ],
  "reason": "DSAR-2026-Q2-014"
}

Sample response

{
  "deleted": [
    "emb_sha256_8c2e...a1f",
    "emb_sha256_3b41...c9d"
  ],
  "not_found": [],
  "request_id": "req_01HXT9F32",
  "ico_audit_ref": "ZA-pending-2026-06-15-001"
}

Notes

  • Deletion is hard — the embedding and all derived match events are tombstoned within 24 hours. There is no soft-delete window.
  • The reason field is mandatory on bulk; we store it (as plain text) against the ICO audit ref so a DSAR auditor can trace every deletion back to its origin.
  • The 14-day automatic expiry runs independently. You almost never need to call this — but the endpoint exists so a customer DPO can satisfy an immediate erasure request without waiting for the expiry.

Want the OpenAPI YAML?

The OpenAPI 3.1 source-of-truth lives in the customer portal, which goes live with the pilot agreement. We're happy to walk through the YAML on a call before that — schedule one at /book.