kcv-sdk
v1.0.3
Published
TypeScript/JavaScript SDK for the KCV (Khmer Computer Vision) REST API — OCR extraction, passive face liveness, and face comparison.
Maintainers
Readme
kcv-sdk
A production-ready, lightweight TypeScript/JavaScript SDK for the KCV (Khmer Computer Vision) REST API. It provides strongly typed models, a fluent client, pluggable
authentication, automatic retries, and a rich error hierarchy — with zero runtime
dependencies (built on the native fetch), shipping both ESM and CommonJS with full type
declarations.
The current API surface covers OCR document extraction (
POST /ocr/extract), passive face liveness (POST /face/liveness/passive), and face comparison (POST /face/compare). The SDK is architected so additional endpoints can be added without touching the transport layer.
This is the TypeScript port of the Java SDK (java-kcv-sdk) and mirrors its feature set.
Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- Configuration
- Auto-Configuration (Environment)
- Endpoints & Usage Examples
- Response Models
- Error Handling
- Retry & Resilience
- Logging
- Proxy & TLS
- Best Practices
- Assumptions
- Versioning
- Publishing
- Changelog
- License
Features
- ✅ Fluent client — options object (
new KcvClient({ ... })) or builder (KcvClient.builder()...build()) - ✅ Auto-configuration from the environment —
kcv()/createClientFromEnv()readKCV_*vars, zero wiring - ✅ Strongly typed request/response models (fully nested) for every endpoint
- ✅ ESM + CommonJS dual package with bundled
.d.tstype declarations - ✅ Zero runtime dependencies — built on the native
fetch(Node 18+) - ✅ Pluggable authentication: API key (
x-api-key), bearer, basic, custom header - ✅ Automatic JSON (de)serialization with camelCase normalization of responses
- ✅ Configurable retry with exponential backoff (
429/502/503/504+ network timeouts), honoringRetry-After - ✅ Typed error hierarchy mapped from HTTP status codes
- ✅ Pluggable logging (never logs credentials or image payloads)
- ✅ Transparent gzip, request timeouts, and optional proxy / TLS control (via
undici)
Requirements
| Requirement | Version |
| ----------- | ---------------------------- |
| Node.js | ≥ 18 (native fetch) |
| TypeScript | ≥ 5 (optional; JS works too) |
For older runtimes without a global fetch, pass your own via the fetch option.
Installation
npm install kcv-sdk
# or: pnpm add kcv-sdk / yarn add kcv-sdkQuick Start
ESM / TypeScript
import { KcvClient } from 'kcv-sdk';
import { readFile } from 'node:fs/promises';
const client = new KcvClient({
baseUrl: 'https://<kcv-api-gateway>/kcv',
apiKey: 'kcv_sk_uat_xxx',
timeoutMs: 30_000,
retryCount: 3,
});
const image = (await readFile('id.jpg')).toString('base64');
const result = await client.ocr.extract({ image, crop: false });
console.log('ID number:', result.physical?.idNumber);
console.log('Name (EN):', result.physical?.fullNameEn);
console.log('MRZ valid:', result.mrz?.valid);CommonJS
const { KcvClient } = require('kcv-sdk');
const client = new KcvClient({ apiKey: 'kcv_sk_uat_xxx' });
const result = await client.ocr.extract({ image });Builder (parity with the Java SDK)
const client = KcvClient.builder()
.baseUrl('https://<kcv-api-gateway>/kcv')
.apiKey('kcv_sk_uat_xxx')
.timeout(30_000)
.retryCount(3)
.build();Zero-config (from environment)
import { kcv } from 'kcv-sdk';
// Reads KCV_API_KEY / KCV_BASE_URL / … from process.env on first use, then caches the client.
const result = await kcv().ocr.extract({ image });See Auto-Configuration (Environment) for all recognized variables.
Reuse a single client instance across requests. If you enable a proxy or disable TLS
verification, call await client.close() on shutdown to release the underlying dispatcher.
Configuration
All options (everything is optional):
| Option | Type | Default | Description |
| ------------------------ | ------------------------ | -------------- | ------------------------------------------------------- |
| baseUrl | string | UAT gateway | API base URL (trailing slash trimmed) |
| apiKey | string | — | API key sent as x-api-key |
| apiKeyHeader | string | x-api-key | Custom API-key header name |
| bearerToken | string | — | Authorization: Bearer <token> |
| basicAuth | { username, password } | — | HTTP Basic auth |
| authenticationProvider | AuthenticationProvider | — | Fully custom auth strategy (wins over the above) |
| timeoutMs | number | 30000 | Per-request timeout in ms (0 disables) |
| retryCount | number | 2 | Automatic retries for transient failures |
| retryBaseDelayMs | number | 500 | Initial exponential-backoff delay |
| retryMaxDelayMs | number | 10000 | Cap for a single backoff step |
| headers | Record<string,string> | — | Extra headers on every request |
| sslVerification | boolean | true | Disable only for trusted test envs (needs undici) |
| proxyUrl | string | — | HTTP(S) proxy (needs undici) |
| dispatcher | unknown | — | Advanced: an undici Dispatcher passed to fetch |
| fetch | FetchLike | global fetch | Override the fetch implementation |
| logger | Logger | no-op | Debug logging sink |
Authentication
The SDK auto-detects the API-key method (x-api-key) and injects it on every request. Other
schemes are supported (precedence: authenticationProvider → apiKey → bearerToken →
basicAuth):
new KcvClient({ apiKey: 'kcv_sk_uat_xxx' }); // x-api-key
new KcvClient({ apiKey: 'kcv_sk_uat_xxx', apiKeyHeader: 'x-api-key' });
new KcvClient({ bearerToken: 'eyJhbGci...' }); // Authorization: Bearer
new KcvClient({ basicAuth: { username: 'u', password: 'p' } }); // Authorization: Basic
new KcvClient({ authenticationProvider: { apply: (h) => (h['X-Sig'] = sign()) } });Credentials are never logged.
Auto-Configuration (Environment)
Prefer zero manual wiring? Configure the client entirely from environment variables — the JS
analog of the Java SDK's Spring Boot auto-configuration (which binds kcv.* from
application.yml).
import { kcv } from 'kcv-sdk';
// Auto-configured from process.env on first use, then cached process-wide:
const result = await kcv().ocr.extract({ image });# .env / deployment environment
KCV_API_KEY=kcv_sk_uat_xxx
KCV_BASE_URL=https://<kcv-api-gateway>/kcv
KCV_TIMEOUT_MS=30000
KCV_RETRY_COUNT=3For an explicit instance, use createClientFromEnv() — optionally merging overrides on top of
the environment (overrides win) — or optionsFromEnv() to get just the resolved options:
import { createClientFromEnv, optionsFromEnv } from 'kcv-sdk';
const client = createClientFromEnv(); // pure environment
const client2 = createClientFromEnv({ retryCount: 5 }); // env + overrides
const opts = optionsFromEnv(); // resolved options objectRecognized variables
| Variable | Maps to | Notes |
| ------------------------------------------- | ------------------ | --------------------------------------- |
| KCV_BASE_URL | baseUrl | |
| KCV_API_KEY | apiKey | sent as x-api-key |
| KCV_API_KEY_HEADER | apiKeyHeader | default x-api-key |
| KCV_BEARER_TOKEN | bearerToken | |
| KCV_BASIC_USERNAME / KCV_BASIC_PASSWORD | basicAuth | |
| KCV_TIMEOUT_MS | timeoutMs | integer ms |
| KCV_RETRY_COUNT | retryCount | integer |
| KCV_RETRY_BASE_DELAY_MS | retryBaseDelayMs | integer ms |
| KCV_RETRY_MAX_DELAY_MS | retryMaxDelayMs | integer ms |
| KCV_SSL_VERIFICATION | sslVerification | true/false/1/0/yes/no |
| KCV_PROXY_URL | proxyUrl | needs the optional undici package |
| KCV_HEADERS | headers | JSON object, e.g. {"x-tenant":"acme"} |
| KCV_DEBUG | logger | true wires a console logger |
Authentication precedence matches the client (apiKey → bearerToken → basicAuth). Malformed
numeric/boolean/JSON values throw a KcvError at construction so misconfiguration surfaces early.
In tests, call resetDefaultClient() to force kcv() to re-read the environment.
Endpoints & Usage Examples
Runnable examples live under examples/: ocr-extract.ts
and face-verification.ts.
POST /ocr/extract — OCR document extraction
const res = await client.ocr.extract({
image: base64Image, // required
crop: false, // optional, defaults to false
});| Field | Type | Required | Description |
| ------- | --------- | -------- | -------------------------------------- |
| image | string | yes | Base64-encoded document image |
| crop | boolean | no | Auto-crop before OCR (default false) |
POST /face/liveness/passive — passive liveness detection
const res = await client.face.passiveLiveness({ image: base64Image });
if (res.isLive) {
// proceed
} else {
console.log('Spoof suspected:', res.status, 'pFake=', res.scores?.pFake);
}| Field | Type | Required | Description |
| ------- | -------- | -------- | ------------------------- |
| image | string | yes | Base64-encoded face image |
POST /face/compare — face comparison
const res = await client.face.compare({
sourceImage: selfieBase64, // required
targetImage: idPortraitBase64, // required
});
const similarity = res.faceMatches?.[0]?.similarity ?? 0; // 0–100| Field | Type | Required | Description |
| ------------- | -------- | -------- | -------------------------------- |
| sourceImage | string | yes | Base64-encoded source face image |
| targetImage | string | yes | Base64-encoded target face image |
Response Models
Response keys are normalized to camelCase (e.g. document_type → documentType,
Similarity → similarity). Dates are ISO-8601 strings (yyyy-MM-dd).
OcrExtractResponse
├── correlationId, requestId, documentType, countryCode
├── physical: Physical (idNumber, fullNameKh/En, sex, nationality, dateOfBirth,
│ placeOfBirth, currentAddress, issueDate, expiryDate, physicalAttributes)
├── mrz: Mrz (rawLine1..3, documentNumber, valid, nameMatchesPrinted, ...)
├── raw: Raw (fullText, engineVersion)
└── lines: string[]
LivenessResponse
├── isLive, result, status
├── scores: LivenessScores (pReal, pFake)
├── gate, verdictStrict, verdictAny
├── models: LivenessModel[] (name, available, pReal, threshold, real)
├── faceDetected, faceBox: number[], facesFound, threshold, latencyMs
FaceCompareResponse
├── faceMatches: FaceMatch[] (similarity: number // 0–100)
└── latencyMsAll model interfaces are exported for use in your own types.
Error Handling
Every SDK error extends KcvError. HTTP errors map to specific subclasses of KcvApiError:
| HTTP status | Error |
| ----------------- | ---------------------------------------------- |
| 400 | ValidationError |
| 401 | AuthenticationError |
| 403 | ForbiddenError |
| 404 | ResourceNotFoundError |
| 409 | ConflictError |
| 429 | RateLimitError (exposes retryAfterSeconds) |
| 5xx | ServerError |
| other ≥ 400 | KcvApiError |
| network / timeout | NetworkError |
Every KcvApiError exposes statusCode, errorBody, rawBody, correlationId, and requestId.
import {
KcvError,
ValidationError,
AuthenticationError,
RateLimitError,
NetworkError,
} from 'kcv-sdk';
try {
await client.ocr.extract({ image });
} catch (err) {
if (err instanceof ValidationError) console.warn('Bad request:', err.errorBody);
else if (err instanceof AuthenticationError) console.error('Bad API key');
else if (err instanceof RateLimitError) console.warn('Retry after', err.retryAfterSeconds, 's');
else if (err instanceof NetworkError) console.error('Cannot reach KCV:', err.message);
else if (err instanceof KcvError) throw err; // any other SDK error
}Retry & Resilience
The SDK automatically retries only transient failures:
- HTTP
429,502,503,504 - Network errors and timeouts
Backoff is exponential (retryBaseDelayMs × 2^(attempt-1), capped at retryMaxDelayMs). A
Retry-After header on a 429 is honored (capped by retryMaxDelayMs). Non-retryable statuses
(e.g. 400, 401, 404) fail fast. Set retryCount: 0 to disable retries.
Logging
Pass a logger to receive debug output (request method/URL, response status, timing, and
gateway correlationId/requestId). Credentials and image payloads are never logged.
import { KcvClient, consoleLogger } from 'kcv-sdk';
const client = new KcvClient({ apiKey: 'kcv_sk_uat_xxx', logger: consoleLogger() });Provide any object implementing the Logger interface ({ debug, info, warn, error }) to route
logs into your own framework. The default is a no-op.
Proxy & TLS
Native fetch has no built-in proxy support, so proxy and TLS-verification control use
undici (an optional peer dependency):
npm install undici// via convenience options (the SDK builds an undici dispatcher lazily)
const client = new KcvClient({ apiKey: 'kcv_sk_uat_xxx', proxyUrl: 'http://proxy.internal:8080' });
// or pass your own dispatcher for full control
import { ProxyAgent } from 'undici';
const client2 = new KcvClient({
apiKey: 'kcv_sk_uat_xxx',
dispatcher: new ProxyAgent('http://proxy.internal:8080'),
});
await client.close(); // releases a dispatcher the SDK created⚠️
sslVerification: falsedisables TLS certificate checks and is for trusted test gateways only — never in production.
Best Practices
- Reuse one client across requests; construct it once at startup.
- Close on shutdown (
await client.close()) when using a proxy / custom dispatcher. - Keep TLS verification on in production.
- Store keys securely (env vars / secret manager), never in source control.
- Narrow errors with
instanceof; fall back toKcvError. - Tune
timeoutMsandretryCountto your latency/SLA needs.
Assumptions
The KCV API documents each endpoint's success response but leaves some details unspecified. Where ambiguous, the SDK makes documented, conservative choices:
- Base URL split.
.../kcv/<path>is split into base path.../kcvand the endpoint path. OverridebaseUrlfor production. - Error schema. Not formally defined;
ApiErrorBodymaps common gateway fields and tolerates unknown ones; the raw body is always available viaerror.rawBody. - Status-code mapping. Standard REST conventions (400 → validation, 401 → auth, …).
- Dates are kept as ISO-8601 strings (
yyyy-MM-dd) to avoid timezone ambiguity. - Retryable statuses:
429/502/503/504and network timeouts. faceBoxorder is exposed as a rawnumber[]; the API does not label its four values.Similaritycasing: the capitalized"Similarity"key is normalized toFaceMatch.similarity(anumber, 0–100).
Versioning
This project follows Semantic Versioning. The current version is 1.0.0. Breaking changes bump the major version; new backward-compatible endpoints/fields bump the minor version.
Publishing
Released to npm as kcv-sdk. See
PUBLISHING.md for the full release process (npm publish, provenance, and a
GitHub Actions example).
Changelog
See CHANGELOG.md.
License
Apache License 2.0 © Khmer Computer Vision (KCV).
