src/config/env.ts) — the process refuses to boot on a missing or malformed value, rather than failing on the first request that needs it.
Infrastructure
required
Postgres connection string. Must point at a database with the
vector extension enabled (CREATE EXTENSION vector;) — see Architecture → Vector Index.required
Used for OTP codes, rate limits, idempotency-adjacent short-lived tokens (
validation_token, link_token, proof_token), and liveness lockouts.External dependencies
required
Base URL of the face-engine service (
/v1/embed, /v1/compare, /v1/liveness, /v1/health, /v1/version).required
Base URL of the nin_bvn_simulator (
/api/{nin,bvn}/{number}/validate, /photo).default:"console"
console is the only implementation today — logs the OTP code instead of sending SMS. See src/clients/OTPClient.ts.Auth
required
Signs access tokens. Refresh tokens are opaque UUIDs stored server-side in
Session, not JWTs.default:"900"
default:"2592000"
Matching & liveness
default:"0.4"
Kept equal to face-engine’s own
COMPARE_THRESHOLD default so a decision doesn’t silently drift depending on whether the comparison happened inside face-engine (/v1/compare) or inside this service (cosine similarity against a stored vector, for self-verify and cross-registry checks).default:"stub"
stub (always passes, loud startup warning) or heuristic (calls face-engine’s real /v1/liveness — itself a passive heuristic, not certified PAD). See src/clients/LivenessClient.ts.Token TTLs
default:"300"
default:"300"
Single-use, short-expiry by design — the API’s guarantee that a passing
verify-face actually happened before link is allowed to complete.default:"300"
How long a passing NIN capture stays reusable for a BVN
verify-face in the same session.default:"120"
Gates
PATCH/DELETE /users/{id} — requires a fresh passing /verify/self.default:"60"
Server-enforced lockout after a liveness failure, on both
/identify and /verify/self.Other
default:"arcface-w600k_r50-v1"
Recorded against every canonical embedding written.
GET /models also queries face-engine’s own /v1/version and surfaces both.required
Keys the AES-256-GCM encryption used to store NIN/BVN plaintext at rest (
src/lib/piiCrypto.ts) — a KMS-backed key is a flagged production-readiness item, not this dev default.Local port remapping
docker-compose.yml maps Postgres to host port 5433 and Redis to 6380, not the standard 5432/6379 — this avoids clashing with a locally-installed Postgres/Redis that may already own those ports. Adjust DATABASE_URL/REDIS_URL to match if you change the compose file.