@kyvshield/rest-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,372 @@
1
+ # @kyvshield/rest-sdk
2
+
3
+ Fully typed Node.js / TypeScript SDK for the [KyvShield](https://kyvshield.com) KYC REST API.
4
+
5
+ Requires **Node.js 18+** (uses native `fetch` and `crypto`).
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @kyvshield/rest-sdk
13
+ # or
14
+ yarn add @kyvshield/rest-sdk
15
+ # or
16
+ pnpm add @kyvshield/rest-sdk
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Quick start
22
+
23
+ ```typescript
24
+ import { KyvShield } from '@kyvshield/rest-sdk';
25
+
26
+ const kyv = new KyvShield('your-api-key');
27
+
28
+ const result = await kyv.verify({
29
+ steps: ['selfie', 'recto', 'verso'],
30
+ target: 'SN-CIN',
31
+ language: 'fr',
32
+ challengeMode: 'standard',
33
+ requireFaceMatch: true,
34
+ images: {
35
+ // key = {step}_{challenge}
36
+ selfie_center_face: './images/selfie.jpg',
37
+ selfie_close_eyes: './images/selfie_eyes_closed.jpg',
38
+ recto_center_document: './images/recto.jpg',
39
+ recto_tilt_left: './images/recto_tilt_left.jpg',
40
+ verso_center_document: './images/verso.jpg',
41
+ },
42
+ });
43
+
44
+ if (result.overall_status === 'pass') {
45
+ console.log('KYC passed!', result.session_id);
46
+ } else {
47
+ console.warn('KYC rejected:', result.steps.map(s => s.user_messages).flat());
48
+ }
49
+ ```
50
+
51
+ ---
52
+
53
+ ## API Reference
54
+
55
+ ### `new KyvShield(apiKey, baseUrl?)`
56
+
57
+ | Parameter | Type | Default | Description |
58
+ |-----------|----------|-------------------------------------------------|--------------------------------|
59
+ | `apiKey` | `string` | — | Your KyvShield API key |
60
+ | `baseUrl` | `string` | `https://kyvshield-naruto.innolinkcloud.com` | Override for staging/local use |
61
+
62
+ ```typescript
63
+ // Production (default)
64
+ const kyv = new KyvShield('your-api-key');
65
+
66
+ // Local development
67
+ const kyv = new KyvShield('your-api-key', 'http://localhost:8080');
68
+ ```
69
+
70
+ ---
71
+
72
+ ### `kyv.getChallenges()`
73
+
74
+ Returns the available challenge names grouped by type (`document` / `selfie`) and mode (`minimal` / `standard` / `strict`).
75
+
76
+ Use this to discover which image keys you need to provide for a given `challengeMode`.
77
+
78
+ ```typescript
79
+ const { challenges } = await kyv.getChallenges();
80
+
81
+ // challenges.document.standard => ['center_document', 'tilt_left', 'tilt_right']
82
+ // challenges.selfie.minimal => ['center_face', 'close_eyes']
83
+ ```
84
+
85
+ **Return type:** [`ChallengesResponse`](#challengesresponse)
86
+
87
+ ---
88
+
89
+ ### `kyv.verify(options)`
90
+
91
+ Submit a KYC verification request. Reads image files from disk and sends them as multipart form data.
92
+
93
+ ```typescript
94
+ const result = await kyv.verify({
95
+ steps: ['selfie', 'recto'],
96
+ target: 'SN-CIN',
97
+ language: 'en',
98
+ challengeMode: 'minimal',
99
+ requireFaceMatch: true,
100
+ kycIdentifier: 'user-ref-42',
101
+ images: {
102
+ selfie_center_face: './selfie.jpg',
103
+ recto_center_document: './recto.jpg',
104
+ },
105
+ });
106
+ ```
107
+
108
+ **Options:** [`VerifyOptions`](#verifyoptions)
109
+
110
+ **Return type:** [`KycResponse`](#kycresponse)
111
+
112
+ ---
113
+
114
+ ### `KyvShield.verifyWebhookSignature(payload, apiKey, signatureHeader)`
115
+
116
+ Static method. Validates an incoming KyvShield webhook using HMAC-SHA256.
117
+
118
+ ```typescript
119
+ // Express example
120
+ import express from 'express';
121
+ import { KyvShield } from '@kyvshield/rest-sdk';
122
+
123
+ const app = express();
124
+
125
+ app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
126
+ const valid = KyvShield.verifyWebhookSignature(
127
+ req.body, // Buffer
128
+ process.env.KYVSHIELD_API_KEY!,
129
+ req.headers['x-kyvshield-signature'] as string,
130
+ );
131
+
132
+ if (!valid) return res.status(401).send('Invalid signature');
133
+
134
+ const event = JSON.parse(req.body.toString());
135
+ console.log('Webhook event:', event);
136
+ res.sendStatus(200);
137
+ });
138
+ ```
139
+
140
+ | Parameter | Type | Description |
141
+ |---------------------|----------|-------------------------------------------------------|
142
+ | `payload` | `Buffer` | Raw request body |
143
+ | `apiKey` | `string` | Your KyvShield API key |
144
+ | `signatureHeader` | `string` | Value of `X-KyvShield-Signature` from the HTTP header |
145
+
146
+ Returns `true` if the signature is valid, `false` otherwise.
147
+
148
+ ---
149
+
150
+ ## Types Reference
151
+
152
+ ### `VerifyOptions`
153
+
154
+ ```typescript
155
+ interface VerifyOptions {
156
+ /** Steps to execute in order. */
157
+ steps: Step[]; // e.g. ['selfie', 'recto', 'verso']
158
+
159
+ /** Document type. */
160
+ target: DocumentTarget; // e.g. 'SN-CIN'
161
+
162
+ /** Response language. Default: 'fr' */
163
+ language?: Language; // 'fr' | 'en' | 'wo'
164
+
165
+ /** Global challenge intensity. Default: 'standard' */
166
+ challengeMode?: ChallengeMode; // 'minimal' | 'standard' | 'strict'
167
+
168
+ /** Per-step overrides */
169
+ selfieChallengeMode?: ChallengeMode;
170
+ rectoChallengeMode?: ChallengeMode;
171
+ versoChallengeMode?: ChallengeMode;
172
+
173
+ /** Whether to match selfie face against document photo. Default: false */
174
+ requireFaceMatch?: boolean;
175
+
176
+ /** Your internal reference ID, echoed back in the response. */
177
+ kycIdentifier?: string;
178
+
179
+ /**
180
+ * Map of images to submit.
181
+ * Key format: `{step}_{challenge}`, e.g. 'recto_center_document'
182
+ * Value: path to the image file on disk.
183
+ */
184
+ images: Record<string, string>;
185
+ }
186
+ ```
187
+
188
+ ### `KycResponse`
189
+
190
+ ```typescript
191
+ interface KycResponse {
192
+ success: boolean;
193
+ session_id: string;
194
+ overall_status: 'pass' | 'reject';
195
+ overall_confidence: number; // 0–1
196
+ processing_time_ms: number;
197
+ face_verification?: FaceVerification;
198
+ steps: StepResult[];
199
+ }
200
+ ```
201
+
202
+ ### `StepResult`
203
+
204
+ ```typescript
205
+ interface StepResult {
206
+ step_index: number;
207
+ step_type: 'selfie' | 'recto' | 'verso';
208
+ success: boolean;
209
+ processing_time_ms: number;
210
+
211
+ liveness: {
212
+ is_live: boolean;
213
+ score: number; // 0–1
214
+ confidence: 'HIGH' | 'MEDIUM' | 'LOW';
215
+ };
216
+
217
+ verification: {
218
+ is_authentic: boolean;
219
+ confidence: number; // 0–1
220
+ checks_passed: string[];
221
+ fraud_indicators: string[];
222
+ warnings: string[];
223
+ issues: string[];
224
+ };
225
+
226
+ user_messages: string[];
227
+
228
+ // Document steps (recto / verso) only:
229
+ aligned_document?: string; // base64 JPEG
230
+ extraction?: ExtractionField[];
231
+ extracted_photos?: ExtractedPhoto[];
232
+
233
+ // Selfie step only:
234
+ captured_image?: string; // base64 JPEG
235
+ }
236
+ ```
237
+
238
+ ### `ExtractionField`
239
+
240
+ ```typescript
241
+ interface ExtractionField {
242
+ key: string;
243
+ document_key: string;
244
+ label: string;
245
+ value: string;
246
+ display_priority: number;
247
+ icon?: string;
248
+ }
249
+ ```
250
+
251
+ ### `ExtractedPhoto`
252
+
253
+ ```typescript
254
+ interface ExtractedPhoto {
255
+ image: string; // base64 JPEG
256
+ confidence: number; // 0–1
257
+ bbox: number[]; // [x, y, width, height]
258
+ area: number;
259
+ width: number;
260
+ height: number;
261
+ }
262
+ ```
263
+
264
+ ### `FaceVerification`
265
+
266
+ ```typescript
267
+ interface FaceVerification {
268
+ is_match: boolean;
269
+ similarity_score: number; // 0–100
270
+ }
271
+ ```
272
+
273
+ ### `ChallengesResponse`
274
+
275
+ ```typescript
276
+ interface ChallengesResponse {
277
+ challenges: {
278
+ document: { minimal: string[]; standard: string[]; strict: string[] };
279
+ selfie: { minimal: string[]; standard: string[]; strict: string[] };
280
+ };
281
+ }
282
+ ```
283
+
284
+ ---
285
+
286
+ ## Error handling
287
+
288
+ All API errors throw a `KyvShieldError`:
289
+
290
+ ```typescript
291
+ import { KyvShield, KyvShieldError } from '@kyvshield/rest-sdk';
292
+
293
+ try {
294
+ const result = await kyv.verify({ ... });
295
+ } catch (e) {
296
+ if (e instanceof KyvShieldError) {
297
+ console.error('Status code:', e.statusCode); // e.g. 401, 422, 500
298
+ console.error('Body:', e.body); // parsed JSON body when available
299
+ console.error('Message:', e.message);
300
+ }
301
+ }
302
+ ```
303
+
304
+ The SDK also throws `KyvShieldError` (without a `statusCode`) for:
305
+ - Empty API key in the constructor
306
+ - Empty `steps` array
307
+ - Empty `target` string
308
+ - Empty `images` map
309
+ - Image file path that does not exist on disk
310
+
311
+ ---
312
+
313
+ ## Building
314
+
315
+ ```bash
316
+ npm install
317
+ npm run build # outputs to ./dist
318
+ npm run lint # type-check only, no emit
319
+ npm run build:watch
320
+ ```
321
+
322
+ ---
323
+
324
+ ## Running the test suite
325
+
326
+ Ensure the KyvShield backend is running locally on port 8080, then:
327
+
328
+ ```bash
329
+ npx ts-node --esm test.ts
330
+ ```
331
+
332
+ The test script covers:
333
+ - Constructor validation
334
+ - Webhook signature verification (HMAC-SHA256, timing-safe comparison)
335
+ - `VerifyOptions` validation (no network)
336
+ - `getChallenges()` (network)
337
+ - `verify()` with a single recto step (network)
338
+ - `verify()` with recto + verso steps (network)
339
+
340
+ ---
341
+
342
+ ## Challenge modes
343
+
344
+ | Mode | Document challenges | Selfie challenges |
345
+ |------------|----------------------------------------------------------------------------|-----------------------------------------------------------------|
346
+ | `minimal` | `center_document` | `center_face`, `close_eyes` |
347
+ | `standard` | `center_document`, `tilt_left`, `tilt_right` | `center_face`, `close_eyes`, `turn_left`, `turn_right` |
348
+ | `strict` | `center_document`, `tilt_left`, `tilt_right`, `tilt_forward`, `tilt_back` | `center_face`, `close_eyes`, `turn_left`, `turn_right`, `smile`, `look_up`, `look_down` |
349
+
350
+ The image map key for each image is `{step}_{challenge}`, e.g.:
351
+ - `recto_center_document`
352
+ - `recto_tilt_left`
353
+ - `selfie_center_face`
354
+ - `selfie_turn_right`
355
+
356
+ ---
357
+
358
+ ## Supported document targets
359
+
360
+ | Value | Document |
361
+ |-----------------------|---------------------------------|
362
+ | `SN-CIN` | Carte d'Identité Nationale (SN) |
363
+ | `SN-PASSPORT` | Passeport sénégalais |
364
+ | `SN-DRIVER-LICENCE` | Permis de conduire (SN) |
365
+
366
+ Custom target strings are also accepted.
367
+
368
+ ---
369
+
370
+ ## License
371
+
372
+ MIT
@@ -0,0 +1,163 @@
1
+ /**
2
+ * KyvShield REST SDK — Node.js / TypeScript
3
+ *
4
+ * A fully typed SDK for the KyvShield KYC REST API.
5
+ * Requires Node.js 18+ (uses native fetch and crypto).
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { KyvShield } from '@kyvshield/rest-sdk';
10
+ *
11
+ * const kyv = new KyvShield('your-api-key');
12
+ *
13
+ * const result = await kyv.verify({
14
+ * steps: ['selfie', 'recto', 'verso'],
15
+ * target: 'SN-CIN',
16
+ * language: 'fr',
17
+ * challengeMode: 'standard',
18
+ * requireFaceMatch: true,
19
+ * images: {
20
+ * selfie_center_face: './selfie.jpg',
21
+ * recto_center_document: './recto.jpg',
22
+ * verso_center_document: './verso.jpg',
23
+ * },
24
+ * });
25
+ *
26
+ * console.log(result.overall_status); // 'pass' | 'reject'
27
+ * ```
28
+ */
29
+ import type { ChallengesResponse, KycResponse, KyvShieldErrorDetails, VerifyOptions } from './types.js';
30
+ export * from './types.js';
31
+ /**
32
+ * Error thrown when the KyvShield API returns a non-2xx response
33
+ * or when a request cannot be completed.
34
+ */
35
+ export declare class KyvShieldError extends Error {
36
+ readonly statusCode?: number;
37
+ readonly body?: unknown;
38
+ constructor(message: string, details?: KyvShieldErrorDetails);
39
+ }
40
+ /**
41
+ * KyvShield SDK client.
42
+ *
43
+ * Instantiate once and reuse across your application.
44
+ */
45
+ export declare class KyvShield {
46
+ private readonly apiKey;
47
+ private readonly baseUrl;
48
+ /**
49
+ * Create a new KyvShield client.
50
+ *
51
+ * @param apiKey - Your KyvShield API key (sent as the `X-API-Key` header).
52
+ * @param baseUrl - Optional base URL override. Defaults to the production endpoint.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * // Production
57
+ * const kyv = new KyvShield('your-api-key');
58
+ *
59
+ * // Local / staging
60
+ * const kyv = new KyvShield('your-api-key', 'http://localhost:8080');
61
+ * ```
62
+ */
63
+ constructor(apiKey: string, baseUrl?: string);
64
+ /**
65
+ * Retrieve the list of available challenges for each mode and step type.
66
+ *
67
+ * Useful for dynamically building the image map to pass to {@link verify}.
68
+ *
69
+ * @returns A {@link ChallengesResponse} object describing all available challenges.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * const { challenges } = await kyv.getChallenges();
74
+ * console.log(challenges.selfie.standard);
75
+ * // ['center_face', 'close_eyes', 'turn_left', 'turn_right']
76
+ * ```
77
+ */
78
+ getChallenges(): Promise<ChallengesResponse>;
79
+ /**
80
+ * Submit a KYC verification request.
81
+ *
82
+ * The SDK reads each image from disk, packages everything as a
83
+ * `multipart/form-data` request, and returns the fully typed response.
84
+ *
85
+ * @param options - Verification options. See {@link VerifyOptions} for full documentation.
86
+ * @returns A {@link KycResponse} with per-step results and an overall decision.
87
+ *
88
+ * @throws {@link KyvShieldError} on HTTP errors or missing image files.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * const result = await kyv.verify({
93
+ * steps: ['selfie', 'recto'],
94
+ * target: 'SN-CIN',
95
+ * language: 'en',
96
+ * challengeMode: 'minimal',
97
+ * requireFaceMatch: true,
98
+ * images: {
99
+ * selfie_center_face: './selfie.jpg',
100
+ * recto_center_document: './recto.jpg',
101
+ * },
102
+ * });
103
+ *
104
+ * if (result.overall_status === 'pass') {
105
+ * console.log('Verification passed!', result.session_id);
106
+ * } else {
107
+ * console.warn('Verification rejected', result.steps.map(s => s.user_messages));
108
+ * }
109
+ * ```
110
+ */
111
+ verify(options: VerifyOptions): Promise<KycResponse>;
112
+ /**
113
+ * Verify an incoming webhook signature.
114
+ *
115
+ * KyvShield signs webhook payloads with HMAC-SHA256 using your API key.
116
+ * Call this before processing any webhook to confirm it came from KyvShield.
117
+ *
118
+ * @param payload - Raw request body as a `Buffer`.
119
+ * @param apiKey - Your KyvShield API key (same one used to create the client).
120
+ * @param signatureHeader - Value of the `X-KyvShield-Signature` header sent with the webhook.
121
+ * @returns `true` if the signature is valid, `false` otherwise.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * // Express example
126
+ * app.post('/webhook', express.raw({ type: '*\/*' }), (req, res) => {
127
+ * const valid = KyvShield.verifyWebhookSignature(
128
+ * req.body,
129
+ * process.env.KYVSHIELD_API_KEY!,
130
+ * req.headers['x-kyvshield-signature'] as string,
131
+ * );
132
+ *
133
+ * if (!valid) return res.status(401).send('Invalid signature');
134
+ *
135
+ * // process webhook ...
136
+ * res.sendStatus(200);
137
+ * });
138
+ * ```
139
+ */
140
+ static verifyWebhookSignature(payload: Buffer, apiKey: string, signatureHeader: string): boolean;
141
+ /** Build the common headers used by all non-multipart requests. */
142
+ private buildHeaders;
143
+ /**
144
+ * Validate required fields in {@link VerifyOptions} before sending.
145
+ * Throws a descriptive {@link KyvShieldError} on the first problem found.
146
+ */
147
+ private validateVerifyOptions;
148
+ /**
149
+ * Build the `multipart/form-data` body from {@link VerifyOptions}.
150
+ *
151
+ * Text fields are appended first, followed by binary image fields.
152
+ */
153
+ private buildFormData;
154
+ /**
155
+ * Throw a {@link KyvShieldError} when the HTTP response is not successful (2xx).
156
+ * Tries to parse JSON error details from the body when available.
157
+ */
158
+ private assertOk;
159
+ /** Return a MIME type based on file extension. Defaults to `image/jpeg`. */
160
+ private guessMimeType;
161
+ }
162
+ export default KyvShield;
163
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAOH,OAAO,KAAK,EACV,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,cAAc,YAAY,CAAC;AAS3B;;;GAGG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAgB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpC,SAAgB,IAAI,CAAC,EAAE,OAAO,CAAC;gBAEnB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB;CAM7D;AAID;;;;GAIG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC;;;;;;;;;;;;;;OAcG;gBACS,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,MAAyB;IAW9D;;;;;;;;;;;;;OAaG;IACG,aAAa,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAYlD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACG,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC;IAwB1D;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,MAAM,CAAC,sBAAsB,CAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,GACtB,OAAO;IA0BV,mEAAmE;IACnE,OAAO,CAAC,YAAY;IAOpB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAsB7B;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAkDrB;;;OAGG;YACW,QAAQ;IAgBtB,4EAA4E;IAC5E,OAAO,CAAC,aAAa;CAWtB;AAED,eAAe,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,309 @@
1
+ /**
2
+ * KyvShield REST SDK — Node.js / TypeScript
3
+ *
4
+ * A fully typed SDK for the KyvShield KYC REST API.
5
+ * Requires Node.js 18+ (uses native fetch and crypto).
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { KyvShield } from '@kyvshield/rest-sdk';
10
+ *
11
+ * const kyv = new KyvShield('your-api-key');
12
+ *
13
+ * const result = await kyv.verify({
14
+ * steps: ['selfie', 'recto', 'verso'],
15
+ * target: 'SN-CIN',
16
+ * language: 'fr',
17
+ * challengeMode: 'standard',
18
+ * requireFaceMatch: true,
19
+ * images: {
20
+ * selfie_center_face: './selfie.jpg',
21
+ * recto_center_document: './recto.jpg',
22
+ * verso_center_document: './verso.jpg',
23
+ * },
24
+ * });
25
+ *
26
+ * console.log(result.overall_status); // 'pass' | 'reject'
27
+ * ```
28
+ */
29
+ import * as fs from 'node:fs';
30
+ import * as path from 'node:path';
31
+ import * as crypto from 'node:crypto';
32
+ import FormData from 'form-data';
33
+ export * from './types.js';
34
+ // ─── Constants ────────────────────────────────────────────────────────────────
35
+ const DEFAULT_BASE_URL = 'https://kyvshield-naruto.innolinkcloud.com';
36
+ const API_VERSION = '/api/v1';
37
+ // ─── Error class ─────────────────────────────────────────────────────────────
38
+ /**
39
+ * Error thrown when the KyvShield API returns a non-2xx response
40
+ * or when a request cannot be completed.
41
+ */
42
+ export class KyvShieldError extends Error {
43
+ statusCode;
44
+ body;
45
+ constructor(message, details) {
46
+ super(message);
47
+ this.name = 'KyvShieldError';
48
+ this.statusCode = details?.statusCode;
49
+ this.body = details?.body;
50
+ }
51
+ }
52
+ // ─── SDK Class ────────────────────────────────────────────────────────────────
53
+ /**
54
+ * KyvShield SDK client.
55
+ *
56
+ * Instantiate once and reuse across your application.
57
+ */
58
+ export class KyvShield {
59
+ apiKey;
60
+ baseUrl;
61
+ /**
62
+ * Create a new KyvShield client.
63
+ *
64
+ * @param apiKey - Your KyvShield API key (sent as the `X-API-Key` header).
65
+ * @param baseUrl - Optional base URL override. Defaults to the production endpoint.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * // Production
70
+ * const kyv = new KyvShield('your-api-key');
71
+ *
72
+ * // Local / staging
73
+ * const kyv = new KyvShield('your-api-key', 'http://localhost:8080');
74
+ * ```
75
+ */
76
+ constructor(apiKey, baseUrl = DEFAULT_BASE_URL) {
77
+ if (!apiKey || apiKey.trim() === '') {
78
+ throw new KyvShieldError('apiKey must be a non-empty string');
79
+ }
80
+ this.apiKey = apiKey;
81
+ // Normalise: remove trailing slash so we can always append '/api/v1/...'
82
+ this.baseUrl = baseUrl.replace(/\/+$/, '');
83
+ }
84
+ // ── Public methods ──────────────────────────────────────────────────────────
85
+ /**
86
+ * Retrieve the list of available challenges for each mode and step type.
87
+ *
88
+ * Useful for dynamically building the image map to pass to {@link verify}.
89
+ *
90
+ * @returns A {@link ChallengesResponse} object describing all available challenges.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * const { challenges } = await kyv.getChallenges();
95
+ * console.log(challenges.selfie.standard);
96
+ * // ['center_face', 'close_eyes', 'turn_left', 'turn_right']
97
+ * ```
98
+ */
99
+ async getChallenges() {
100
+ const url = `${this.baseUrl}${API_VERSION}/challenges`;
101
+ const response = await fetch(url, {
102
+ method: 'GET',
103
+ headers: this.buildHeaders(),
104
+ });
105
+ await this.assertOk(response);
106
+ return response.json();
107
+ }
108
+ /**
109
+ * Submit a KYC verification request.
110
+ *
111
+ * The SDK reads each image from disk, packages everything as a
112
+ * `multipart/form-data` request, and returns the fully typed response.
113
+ *
114
+ * @param options - Verification options. See {@link VerifyOptions} for full documentation.
115
+ * @returns A {@link KycResponse} with per-step results and an overall decision.
116
+ *
117
+ * @throws {@link KyvShieldError} on HTTP errors or missing image files.
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * const result = await kyv.verify({
122
+ * steps: ['selfie', 'recto'],
123
+ * target: 'SN-CIN',
124
+ * language: 'en',
125
+ * challengeMode: 'minimal',
126
+ * requireFaceMatch: true,
127
+ * images: {
128
+ * selfie_center_face: './selfie.jpg',
129
+ * recto_center_document: './recto.jpg',
130
+ * },
131
+ * });
132
+ *
133
+ * if (result.overall_status === 'pass') {
134
+ * console.log('Verification passed!', result.session_id);
135
+ * } else {
136
+ * console.warn('Verification rejected', result.steps.map(s => s.user_messages));
137
+ * }
138
+ * ```
139
+ */
140
+ async verify(options) {
141
+ this.validateVerifyOptions(options);
142
+ const form = this.buildFormData(options);
143
+ const url = `${this.baseUrl}${API_VERSION}/kyc/verify`;
144
+ // form-data provides its own headers (with boundary); merge with our auth header
145
+ const headers = {
146
+ 'X-API-Key': this.apiKey,
147
+ ...form.getHeaders(),
148
+ };
149
+ const response = await fetch(url, {
150
+ method: 'POST',
151
+ headers,
152
+ // form.getBuffer() returns a Buffer; Node 18 native fetch accepts it.
153
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
154
+ body: form.getBuffer(),
155
+ });
156
+ await this.assertOk(response);
157
+ return response.json();
158
+ }
159
+ /**
160
+ * Verify an incoming webhook signature.
161
+ *
162
+ * KyvShield signs webhook payloads with HMAC-SHA256 using your API key.
163
+ * Call this before processing any webhook to confirm it came from KyvShield.
164
+ *
165
+ * @param payload - Raw request body as a `Buffer`.
166
+ * @param apiKey - Your KyvShield API key (same one used to create the client).
167
+ * @param signatureHeader - Value of the `X-KyvShield-Signature` header sent with the webhook.
168
+ * @returns `true` if the signature is valid, `false` otherwise.
169
+ *
170
+ * @example
171
+ * ```ts
172
+ * // Express example
173
+ * app.post('/webhook', express.raw({ type: '*\/*' }), (req, res) => {
174
+ * const valid = KyvShield.verifyWebhookSignature(
175
+ * req.body,
176
+ * process.env.KYVSHIELD_API_KEY!,
177
+ * req.headers['x-kyvshield-signature'] as string,
178
+ * );
179
+ *
180
+ * if (!valid) return res.status(401).send('Invalid signature');
181
+ *
182
+ * // process webhook ...
183
+ * res.sendStatus(200);
184
+ * });
185
+ * ```
186
+ */
187
+ static verifyWebhookSignature(payload, apiKey, signatureHeader) {
188
+ if (!payload || !apiKey || !signatureHeader)
189
+ return false;
190
+ const expected = crypto
191
+ .createHmac('sha256', apiKey)
192
+ .update(payload)
193
+ .digest('hex');
194
+ // Use timingSafeEqual to prevent timing attacks
195
+ try {
196
+ const expectedBuf = Buffer.from(expected, 'hex');
197
+ // Strip an optional 'sha256=' prefix the server might prepend
198
+ const incomingSig = signatureHeader.startsWith('sha256=')
199
+ ? signatureHeader.slice(7)
200
+ : signatureHeader;
201
+ const incomingBuf = Buffer.from(incomingSig, 'hex');
202
+ if (expectedBuf.length !== incomingBuf.length)
203
+ return false;
204
+ return crypto.timingSafeEqual(expectedBuf, incomingBuf);
205
+ }
206
+ catch {
207
+ return false;
208
+ }
209
+ }
210
+ // ── Private helpers ─────────────────────────────────────────────────────────
211
+ /** Build the common headers used by all non-multipart requests. */
212
+ buildHeaders() {
213
+ return {
214
+ 'X-API-Key': this.apiKey,
215
+ Accept: 'application/json',
216
+ };
217
+ }
218
+ /**
219
+ * Validate required fields in {@link VerifyOptions} before sending.
220
+ * Throws a descriptive {@link KyvShieldError} on the first problem found.
221
+ */
222
+ validateVerifyOptions(options) {
223
+ if (!options.steps || options.steps.length === 0) {
224
+ throw new KyvShieldError('VerifyOptions.steps must contain at least one step');
225
+ }
226
+ if (!options.target || options.target.trim() === '') {
227
+ throw new KyvShieldError('VerifyOptions.target must be a non-empty string');
228
+ }
229
+ if (!options.images || Object.keys(options.images).length === 0) {
230
+ throw new KyvShieldError('VerifyOptions.images must contain at least one entry');
231
+ }
232
+ // Validate that all image files exist on disk
233
+ for (const [key, filePath] of Object.entries(options.images)) {
234
+ const resolved = path.resolve(filePath);
235
+ if (!fs.existsSync(resolved)) {
236
+ throw new KyvShieldError(`Image file for "${key}" not found: ${resolved}`);
237
+ }
238
+ }
239
+ }
240
+ /**
241
+ * Build the `multipart/form-data` body from {@link VerifyOptions}.
242
+ *
243
+ * Text fields are appended first, followed by binary image fields.
244
+ */
245
+ buildFormData(options) {
246
+ const form = new FormData();
247
+ // ── Text fields ─────────────────────────────────────────────────────────
248
+ // steps is a JSON array string, e.g. '["selfie","recto","verso"]'
249
+ form.append('steps', JSON.stringify(options.steps));
250
+ form.append('target', options.target);
251
+ form.append('language', options.language ?? 'fr');
252
+ form.append('challenge_mode', options.challengeMode ?? 'standard');
253
+ if (options.selfieChallengeMode !== undefined) {
254
+ form.append('selfie_challenge_mode', options.selfieChallengeMode);
255
+ }
256
+ if (options.rectoChallengeMode !== undefined) {
257
+ form.append('recto_challenge_mode', options.rectoChallengeMode);
258
+ }
259
+ if (options.versoChallengeMode !== undefined) {
260
+ form.append('verso_challenge_mode', options.versoChallengeMode);
261
+ }
262
+ form.append('require_face_match', options.requireFaceMatch === true ? 'true' : 'false');
263
+ if (options.kycIdentifier !== undefined) {
264
+ form.append('kyc_identifier', options.kycIdentifier);
265
+ }
266
+ // ── Image files ─────────────────────────────────────────────────────────
267
+ for (const [key, filePath] of Object.entries(options.images)) {
268
+ const resolved = path.resolve(filePath);
269
+ const buffer = fs.readFileSync(resolved);
270
+ const filename = path.basename(resolved);
271
+ const mimeType = this.guessMimeType(filename);
272
+ form.append(key, buffer, {
273
+ filename,
274
+ contentType: mimeType,
275
+ });
276
+ }
277
+ return form;
278
+ }
279
+ /**
280
+ * Throw a {@link KyvShieldError} when the HTTP response is not successful (2xx).
281
+ * Tries to parse JSON error details from the body when available.
282
+ */
283
+ async assertOk(response) {
284
+ if (response.ok)
285
+ return;
286
+ let body;
287
+ try {
288
+ body = await response.json();
289
+ }
290
+ catch {
291
+ body = await response.text().catch(() => undefined);
292
+ }
293
+ throw new KyvShieldError(`KyvShield API error ${response.status}: ${response.statusText}`, { statusCode: response.status, body });
294
+ }
295
+ /** Return a MIME type based on file extension. Defaults to `image/jpeg`. */
296
+ guessMimeType(filename) {
297
+ const ext = path.extname(filename).toLowerCase();
298
+ const map = {
299
+ '.jpg': 'image/jpeg',
300
+ '.jpeg': 'image/jpeg',
301
+ '.png': 'image/png',
302
+ '.webp': 'image/webp',
303
+ '.gif': 'image/gif',
304
+ };
305
+ return map[ext] ?? 'image/jpeg';
306
+ }
307
+ }
308
+ export default KyvShield;
309
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,208 @@
1
+ /**
2
+ * KyvShield REST SDK - Type Definitions
3
+ * All TypeScript interfaces and types for the KyvShield KYC API.
4
+ */
5
+ /** Challenge intensity mode */
6
+ export type ChallengeMode = 'minimal' | 'standard' | 'strict';
7
+ /** Verification step type */
8
+ export type Step = 'selfie' | 'recto' | 'verso';
9
+ /** Supported document target types */
10
+ export type DocumentTarget = 'SN-CIN' | 'SN-PASSPORT' | 'SN-DRIVER-LICENCE' | string;
11
+ /** Supported languages */
12
+ export type Language = 'fr' | 'en' | 'wo';
13
+ /** Overall verification outcome */
14
+ export type OverallStatus = 'pass' | 'reject';
15
+ /** Confidence level descriptor */
16
+ export type ConfidenceLevel = 'HIGH' | 'MEDIUM' | 'LOW';
17
+ /** Available challenges grouped by intensity for a single asset type */
18
+ export interface ChallengeModeMap {
19
+ minimal: string[];
20
+ standard: string[];
21
+ strict: string[];
22
+ }
23
+ /** Full challenges response from GET /api/v1/challenges */
24
+ export interface ChallengesResponse {
25
+ challenges: {
26
+ /** Challenges applicable to document steps (recto / verso) */
27
+ document: ChallengeModeMap;
28
+ /** Challenges applicable to selfie steps */
29
+ selfie: ChallengeModeMap;
30
+ };
31
+ }
32
+ /** A single extracted text field from a document */
33
+ export interface ExtractionField {
34
+ /** Machine-readable field key */
35
+ key: string;
36
+ /** Key as used in the document specification */
37
+ document_key: string;
38
+ /** Human-readable label */
39
+ label: string;
40
+ /** Extracted value */
41
+ value: string;
42
+ /** Display ordering hint (lower = higher priority) */
43
+ display_priority: number;
44
+ /** Optional icon identifier */
45
+ icon?: string;
46
+ }
47
+ /** A photo extracted from a document image */
48
+ export interface ExtractedPhoto {
49
+ /** Base-64-encoded JPEG image data */
50
+ image: string;
51
+ /** Model confidence for this extraction (0–1) */
52
+ confidence: number;
53
+ /** Bounding box [x, y, width, height] in pixels */
54
+ bbox: number[];
55
+ /** Area of the bounding box in pixels² */
56
+ area: number;
57
+ /** Width of the extracted region in pixels */
58
+ width: number;
59
+ /** Height of the extracted region in pixels */
60
+ height: number;
61
+ }
62
+ /** Liveness sub-result for a single step */
63
+ export interface LivenessResult {
64
+ /** Whether the subject / document is considered live / physical */
65
+ is_live: boolean;
66
+ /** Liveness score (0–1) */
67
+ score: number;
68
+ /** Qualitative confidence descriptor */
69
+ confidence: ConfidenceLevel;
70
+ }
71
+ /** Authenticity & fraud sub-result for a single step */
72
+ export interface VerificationResult {
73
+ /** Whether the document / selfie is considered authentic */
74
+ is_authentic: boolean;
75
+ /** Confidence of the authenticity decision (0–1) */
76
+ confidence: number;
77
+ /** List of checks that passed */
78
+ checks_passed: string[];
79
+ /** Detected fraud indicators */
80
+ fraud_indicators: string[];
81
+ /** Non-blocking warnings */
82
+ warnings: string[];
83
+ /** Blocking issues */
84
+ issues: string[];
85
+ }
86
+ /** Result for a single verification step */
87
+ export interface StepResult {
88
+ /** Zero-based index of this step within the submitted steps array */
89
+ step_index: number;
90
+ /** Type of this step */
91
+ step_type: Step;
92
+ /** Whether this step succeeded overall */
93
+ success: boolean;
94
+ /** Server-side processing time for this step in milliseconds */
95
+ processing_time_ms: number;
96
+ /** Liveness detection result */
97
+ liveness: LivenessResult;
98
+ /** Document / selfie authenticity result */
99
+ verification: VerificationResult;
100
+ /** Localised messages intended for display to the end-user */
101
+ user_messages: string[];
102
+ /** Base-64-encoded aligned/deskewed document image (document steps only) */
103
+ aligned_document?: string;
104
+ /** Extracted text fields (document steps only) */
105
+ extraction?: ExtractionField[];
106
+ /** Extracted photos from the document (document steps only) */
107
+ extracted_photos?: ExtractedPhoto[];
108
+ /** Base-64-encoded captured selfie image (selfie step only) */
109
+ captured_image?: string;
110
+ }
111
+ /** Cross-step face-match result */
112
+ export interface FaceVerification {
113
+ /** Whether the selfie face matches the document face */
114
+ is_match: boolean;
115
+ /** Cosine similarity score (0–100) */
116
+ similarity_score: number;
117
+ }
118
+ /** Top-level response from POST /api/v1/kyc/verify */
119
+ export interface KycResponse {
120
+ /** Whether the API call itself succeeded (HTTP-level success) */
121
+ success: boolean;
122
+ /** Unique session identifier for this verification run */
123
+ session_id: string;
124
+ /** Aggregated pass / reject decision across all steps */
125
+ overall_status: OverallStatus;
126
+ /** Aggregated confidence score (0–1) */
127
+ overall_confidence: number;
128
+ /** Total server-side processing time in milliseconds */
129
+ processing_time_ms: number;
130
+ /** Face-match result (present when require_face_match was true) */
131
+ face_verification?: FaceVerification;
132
+ /** Per-step results in submission order */
133
+ steps: StepResult[];
134
+ }
135
+ /**
136
+ * Options for KyvShield.verify().
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * const options: VerifyOptions = {
141
+ * steps: ['selfie', 'recto', 'verso'],
142
+ * target: 'SN-CIN',
143
+ * language: 'fr',
144
+ * challengeMode: 'standard',
145
+ * requireFaceMatch: true,
146
+ * images: {
147
+ * selfie_center_face: '/path/to/selfie.jpg',
148
+ * recto_center_document: '/path/to/recto.jpg',
149
+ * verso_center_document: '/path/to/verso.jpg',
150
+ * },
151
+ * };
152
+ * ```
153
+ */
154
+ export interface VerifyOptions {
155
+ /**
156
+ * Ordered list of steps to execute.
157
+ * Example: `['selfie', 'recto', 'verso']`
158
+ */
159
+ steps: Step[];
160
+ /**
161
+ * Document type to verify against.
162
+ * Example: `'SN-CIN'`
163
+ */
164
+ target: DocumentTarget;
165
+ /**
166
+ * Language for user-facing messages in the response.
167
+ * @default 'fr'
168
+ */
169
+ language?: Language;
170
+ /**
171
+ * Global fallback challenge mode applied to all steps unless overridden.
172
+ * @default 'standard'
173
+ */
174
+ challengeMode?: ChallengeMode;
175
+ /** Per-step challenge mode override for the selfie step */
176
+ selfieChallengeMode?: ChallengeMode;
177
+ /** Per-step challenge mode override for the recto step */
178
+ rectoChallengeMode?: ChallengeMode;
179
+ /** Per-step challenge mode override for the verso step */
180
+ versoChallengeMode?: ChallengeMode;
181
+ /**
182
+ * Whether to perform a cross-step face match between selfie and document photo.
183
+ * @default false
184
+ */
185
+ requireFaceMatch?: boolean;
186
+ /**
187
+ * Optional caller-provided identifier for correlating sessions in your system.
188
+ */
189
+ kycIdentifier?: string;
190
+ /**
191
+ * Map of image files to submit.
192
+ * Keys follow the pattern `{step}_{challenge}`, e.g.:
193
+ * - `'selfie_center_face'`
194
+ * - `'recto_center_document'`
195
+ * - `'recto_tilt_left'`
196
+ *
197
+ * Values are absolute or relative file-system paths to JPEG/PNG images.
198
+ */
199
+ images: Record<string, string>;
200
+ }
201
+ /** Structured error thrown by the SDK */
202
+ export interface KyvShieldErrorDetails {
203
+ /** HTTP status code, if the error originated from an HTTP response */
204
+ statusCode?: number;
205
+ /** Raw response body, if available */
206
+ body?: unknown;
207
+ }
208
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * KyvShield REST SDK - Type Definitions
3
+ * All TypeScript interfaces and types for the KyvShield KYC API.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@kyvshield/rest-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Fully typed Node.js / TypeScript SDK for the KyvShield KYC REST API",
5
+ "author": "KyvShield",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "build:watch": "tsc --watch",
24
+ "clean": "rm -rf dist",
25
+ "prebuild": "npm run clean",
26
+ "test": "npx ts-node --esm test.ts",
27
+ "lint": "tsc --noEmit"
28
+ },
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "keywords": [
33
+ "kyc",
34
+ "kyvshield",
35
+ "identity-verification",
36
+ "liveness",
37
+ "ocr",
38
+ "sdk"
39
+ ],
40
+ "dependencies": {},
41
+ "devDependencies": {
42
+ "@types/node": "^20.0.0",
43
+ "typescript": "^5.4.0"
44
+ }
45
+ }