sms-verification-api 0.9.1 → 0.9.7

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.
@@ -1,162 +1,225 @@
1
- import { Hono } from 'hono'
2
- import { cors } from 'hono/cors'
3
- import { logger } from 'hono/logger'
4
- import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'
1
+ import { cors } from "hono/cors";
2
+ import { logger } from "hono/logger";
3
+ import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
5
4
 
6
5
  // Input/Output Schemas
7
6
  const PersonInputSchema = z.object({
8
- phone_number: z.string().min(10).max(15).describe('Phone number in E.164 or local format'),
9
- legal_name: z.string().min(1).max(100).describe('Full legal name of the person'),
10
- current_address: z.object({
11
- street_line_1: z.string().min(1).max(1000),
12
- street_line_2: z.string().max(1000).optional(),
13
- city: z.string().min(1).max(500),
14
- state_code: z.string().length(2),
15
- postal_code: z.string().min(5).max(10),
16
- country_code: z.string().length(2).default('US')
17
- }).describe('Current address information')
18
- })
7
+ phone_number: z
8
+ .string()
9
+ .min(10)
10
+ .max(15)
11
+ .describe("Phone number in E.164 or local format"),
12
+ legal_name: z
13
+ .string()
14
+ .min(1)
15
+ .max(100)
16
+ .describe("Full legal name of the person"),
17
+ current_address: z
18
+ .object({
19
+ street_line_1: z.string().min(1).max(1000),
20
+ street_line_2: z.string().max(1000).optional(),
21
+ city: z.string().min(1).max(500),
22
+ state_code: z.string().length(2),
23
+ postal_code: z.string().min(5).max(10),
24
+ country_code: z.string().length(2).default("US"),
25
+ })
26
+ .describe("Current address information"),
27
+ });
19
28
 
20
29
  const VerificationResponseSchema = z.object({
21
- verification_score: z.number().min(0).max(100).describe('Confidence score 0-100'),
30
+ verification_score: z
31
+ .number()
32
+ .min(0)
33
+ .max(100)
34
+ .describe("Confidence score 0-100"),
22
35
  name_match_found: z.boolean(),
23
36
  phone_validated: z.boolean(),
24
37
  address_validated: z.boolean(),
25
- questions: z.array(z.object({
26
- id: z.string(),
27
- question: z.string(),
28
- type: z.enum(['address_history', 'phone_history', 'name_verification']),
29
- options: z.array(z.string()).optional()
30
- })),
38
+ questions: z.array(
39
+ z.object({
40
+ id: z.string(),
41
+ question: z.string(),
42
+ type: z.enum(["address_history", "phone_history", "name_verification"]),
43
+ options: z.array(z.string()).optional(),
44
+ }),
45
+ ),
31
46
  historical_data: z.object({
32
47
  previous_addresses: z.array(z.string()),
33
48
  previous_phones: z.array(z.string()),
34
- associated_names: z.array(z.string())
49
+ associated_names: z.array(z.string()),
35
50
  }),
36
- recommendations: z.array(z.string()).describe('Suggestions for additional verification')
37
- })
51
+ recommendations: z
52
+ .array(z.string())
53
+ .describe("Suggestions for additional verification"),
54
+ });
38
55
 
39
56
  // TrestleIQ API Functions
40
- async function makeRequest(endpoint, params, apiKey, baseUrl = 'https://api.trestleiq.com') {
41
- const url = new URL(endpoint, baseUrl)
57
+ interface AddressInput {
58
+ street_line_1: string;
59
+ street_line_2?: string;
60
+ city: string;
61
+ state_code: string;
62
+ postal_code: string;
63
+ country_code?: string;
64
+ }
65
+
66
+ interface PhoneHints {
67
+ name?: string;
68
+ postalCode?: string;
69
+ }
70
+
71
+ interface PersonInput {
72
+ phone_number: string;
73
+ legal_name: string;
74
+ current_address: AddressInput;
75
+ }
76
+
77
+ async function makeRequest(
78
+ endpoint: string,
79
+ params: Record<string, string | undefined>,
80
+ apiKey: string,
81
+ baseUrl = "https://api.trestleiq.com",
82
+ ): Promise<any> {
83
+ const url = new URL(endpoint, baseUrl);
42
84
  Object.entries(params).forEach(([key, value]) => {
43
- if (value) url.searchParams.set(key, value)
44
- })
85
+ if (value) url.searchParams.set(key, value as string);
86
+ });
45
87
 
46
88
  const response = await fetch(url.toString(), {
47
89
  headers: {
48
- 'x-api-key': apiKey,
49
- 'Accept': 'application/json'
50
- }
51
- })
90
+ "x-api-key": apiKey,
91
+ Accept: "application/json",
92
+ },
93
+ });
52
94
 
53
95
  if (!response.ok) {
54
- throw new Error(`TrestleIQ API error: ${response.status} ${response.statusText}`)
96
+ throw new Error(
97
+ `TrestleIQ API error: ${response.status} ${response.statusText}`,
98
+ );
55
99
  }
56
100
 
57
- return response.json()
101
+ return response.json();
58
102
  }
59
103
 
60
- function reversePhone(phoneNumber, apiKey, hints = {}) {
61
- return makeRequest('/3.2/phone', {
62
- phone: phoneNumber,
63
- 'phone.country_hint': 'US',
64
- ...(hints.name && { 'phone.name_hint': hints.name }),
65
- ...(hints.postalCode && { 'phone.postal_code_hint': hints.postalCode })
66
- }, apiKey)
104
+ function reversePhone(phoneNumber: string, apiKey: string, hints: PhoneHints = {}) {
105
+ return makeRequest(
106
+ "/3.2/phone",
107
+ {
108
+ phone: phoneNumber,
109
+ "phone.country_hint": "US",
110
+ ...(hints.name && { "phone.name_hint": hints.name }),
111
+ ...(hints.postalCode && { "phone.postal_code_hint": hints.postalCode }),
112
+ },
113
+ apiKey,
114
+ );
67
115
  }
68
116
 
69
- function findPerson(name, address, apiKey) {
70
- return makeRequest('/3.1/person', {
71
- name,
72
- 'address.street_line_1': address.street_line_1,
73
- 'address.city': address.city,
74
- 'address.state_code': address.state_code,
75
- 'address.postal_code': address.postal_code,
76
- 'address.country_code': address.country_code || 'US'
77
- }, apiKey)
117
+ function findPerson(name: string, address: AddressInput, apiKey: string) {
118
+ return makeRequest(
119
+ "/3.1/person",
120
+ {
121
+ name,
122
+ "address.street_line_1": address.street_line_1,
123
+ "address.city": address.city,
124
+ "address.state_code": address.state_code,
125
+ "address.postal_code": address.postal_code,
126
+ "address.country_code": address.country_code || "US",
127
+ },
128
+ apiKey,
129
+ );
78
130
  }
79
131
 
80
- function reverseAddress(address, apiKey) {
81
- return makeRequest('/3.1/location', {
82
- street_line_1: address.street_line_1,
83
- ...(address.street_line_2 && { street_line_2: address.street_line_2 }),
84
- city: address.city,
85
- state_code: address.state_code,
86
- postal_code: address.postal_code,
87
- country_code: address.country_code || 'US'
88
- }, apiKey)
132
+ function reverseAddress(address: AddressInput, apiKey: string) {
133
+ return makeRequest(
134
+ "/3.1/location",
135
+ {
136
+ street_line_1: address.street_line_1,
137
+ ...(address.street_line_2 && { street_line_2: address.street_line_2 }),
138
+ city: address.city,
139
+ state_code: address.state_code,
140
+ postal_code: address.postal_code,
141
+ country_code: address.country_code || "US",
142
+ },
143
+ apiKey,
144
+ );
89
145
  }
90
146
 
91
147
  // Utility Functions
92
- function normalizeName(name) {
93
- return name.toLowerCase().replace(/[^a-z\s]/g, '').trim()
148
+ function normalizeName(name: string): string {
149
+ return name
150
+ .toLowerCase()
151
+ .replace(/[^a-z\s]/g, "")
152
+ .trim();
94
153
  }
95
154
 
96
- function extractHistoricalData(phoneResult, personResult, addressResult) {
97
- const previousAddresses = []
98
- const previousPhones = []
99
- const associatedNames = []
155
+ function extractHistoricalData(phoneResult: any, personResult: any, addressResult: any) {
156
+ const previousAddresses: string[] = [];
157
+ const previousPhones: string[] = [];
158
+ const associatedNames: string[] = [];
100
159
 
101
160
  // Extract from phone result
102
161
  if (phoneResult?.owners) {
103
- phoneResult.owners.forEach(owner => {
104
- if (owner.name) associatedNames.push(owner.name)
162
+ phoneResult.owners.forEach((owner: any) => {
163
+ if (owner.name) associatedNames.push(owner.name);
105
164
  if (owner.addresses) {
106
- owner.addresses.forEach(addr => {
165
+ owner.addresses.forEach((addr: any) => {
107
166
  if (addr.street_line_1 && addr.city && addr.state_code) {
108
- previousAddresses.push(`${addr.street_line_1}, ${addr.city}, ${addr.state_code}`)
167
+ previousAddresses.push(
168
+ `${addr.street_line_1}, ${addr.city}, ${addr.state_code}`,
169
+ );
109
170
  }
110
- })
171
+ });
111
172
  }
112
173
  if (owner.phones) {
113
- owner.phones.forEach(phone => {
114
- if (phone.phone_number) previousPhones.push(phone.phone_number)
115
- })
174
+ owner.phones.forEach((phone: any) => {
175
+ if (phone.phone_number) previousPhones.push(phone.phone_number);
176
+ });
116
177
  }
117
- })
178
+ });
118
179
  }
119
180
 
120
181
  // Extract from person result
121
182
  if (personResult?.person) {
122
- personResult.person.forEach(person => {
183
+ personResult.person.forEach((person: any) => {
123
184
  if (person.addresses) {
124
- person.addresses.forEach(addr => {
185
+ person.addresses.forEach((addr: any) => {
125
186
  if (addr.street_line_1 && addr.city && addr.state_code) {
126
- previousAddresses.push(`${addr.street_line_1}, ${addr.city}, ${addr.state_code}`)
187
+ previousAddresses.push(
188
+ `${addr.street_line_1}, ${addr.city}, ${addr.state_code}`,
189
+ );
127
190
  }
128
- })
191
+ });
129
192
  }
130
193
  if (person.phones) {
131
- person.phones.forEach(phone => {
132
- if (phone.phone_number) previousPhones.push(phone.phone_number)
133
- })
194
+ person.phones.forEach((phone: any) => {
195
+ if (phone.phone_number) previousPhones.push(phone.phone_number);
196
+ });
134
197
  }
135
- })
198
+ });
136
199
  }
137
200
 
138
201
  // Extract from address result
139
202
  if (addressResult?.current_residents) {
140
- addressResult.current_residents.forEach(resident => {
141
- if (resident.name) associatedNames.push(resident.name)
142
- })
203
+ addressResult.current_residents.forEach((resident: any) => {
204
+ if (resident.name) associatedNames.push(resident.name);
205
+ });
143
206
  }
144
207
 
145
208
  return {
146
209
  previous_addresses: [...new Set(previousAddresses)].slice(0, 10),
147
210
  previous_phones: [...new Set(previousPhones)].slice(0, 5),
148
- associated_names: [...new Set(associatedNames)].slice(0, 10)
149
- }
211
+ associated_names: [...new Set(associatedNames)].slice(0, 10),
212
+ };
150
213
  }
151
214
 
152
- function checkNameMatch(personResult, phoneResult, inputName) {
153
- const normalizedInputName = normalizeName(inputName)
154
-
215
+ function checkNameMatch(personResult: any, phoneResult: any, inputName: string): boolean {
216
+ const normalizedInputName = normalizeName(inputName);
217
+
155
218
  // Check person result
156
219
  if (personResult?.person) {
157
220
  for (const person of personResult.person) {
158
221
  if (person.name && normalizeName(person.name) === normalizedInputName) {
159
- return true
222
+ return true;
160
223
  }
161
224
  }
162
225
  }
@@ -165,27 +228,29 @@ function checkNameMatch(personResult, phoneResult, inputName) {
165
228
  if (phoneResult?.owners) {
166
229
  for (const owner of phoneResult.owners) {
167
230
  if (owner.name && normalizeName(owner.name) === normalizedInputName) {
168
- return true
231
+ return true;
169
232
  }
170
233
  }
171
234
  }
172
235
 
173
- return false
236
+ return false;
174
237
  }
175
238
 
176
- function checkPartialNameMatch(personResult, phoneResult, inputName) {
177
- const inputNameParts = normalizeName(inputName).split(' ')
178
-
179
- const checkNameParts = (name) => {
180
- const nameParts = normalizeName(name).split(' ')
181
- return inputNameParts.some(part => nameParts.includes(part) && part.length > 2)
182
- }
239
+ function checkPartialNameMatch(personResult: any, phoneResult: any, inputName: string): boolean {
240
+ const inputNameParts = normalizeName(inputName).split(" ");
241
+
242
+ const checkNameParts = (name: string): boolean => {
243
+ const nameParts = normalizeName(name).split(" ");
244
+ return inputNameParts.some(
245
+ (part) => nameParts.includes(part) && part.length > 2,
246
+ );
247
+ };
183
248
 
184
249
  // Check person result
185
250
  if (personResult?.person) {
186
251
  for (const person of personResult.person) {
187
252
  if (person.name && checkNameParts(person.name)) {
188
- return true
253
+ return true;
189
254
  }
190
255
  }
191
256
  }
@@ -194,193 +259,245 @@ function checkPartialNameMatch(personResult, phoneResult, inputName) {
194
259
  if (phoneResult?.owners) {
195
260
  for (const owner of phoneResult.owners) {
196
261
  if (owner.name && checkNameParts(owner.name)) {
197
- return true
262
+ return true;
198
263
  }
199
264
  }
200
265
  }
201
266
 
202
- return false
267
+ return false;
203
268
  }
204
269
 
205
- function calculateVerificationScore(phoneResult, personResult, addressResult, input) {
206
- let score = 0
270
+ function calculateVerificationScore(
271
+ phoneResult: any,
272
+ personResult: any,
273
+ addressResult: any,
274
+ input: PersonInput,
275
+ ): number {
276
+ let score = 0;
207
277
 
208
278
  // Phone validation (30 points max)
209
279
  if (phoneResult?.is_valid) {
210
- score += 15
211
- if (phoneResult.line_type === 'Mobile' || phoneResult.line_type === 'Landline') {
212
- score += 10
280
+ score += 15;
281
+ if (
282
+ phoneResult.line_type === "Mobile" ||
283
+ phoneResult.line_type === "Landline"
284
+ ) {
285
+ score += 10;
213
286
  }
214
287
  if (!phoneResult.is_commercial) {
215
- score += 5
288
+ score += 5;
216
289
  }
217
290
  }
218
291
 
219
292
  // Name matching (40 points max)
220
293
  if (checkNameMatch(personResult, phoneResult, input.legal_name)) {
221
- score += 40
222
- } else if (checkPartialNameMatch(personResult, phoneResult, input.legal_name)) {
223
- score += 20
294
+ score += 40;
295
+ } else if (
296
+ checkPartialNameMatch(personResult, phoneResult, input.legal_name)
297
+ ) {
298
+ score += 20;
224
299
  }
225
300
 
226
301
  // Address validation (30 points max)
227
302
  if (addressResult?.is_valid) {
228
- score += 15
303
+ score += 15;
229
304
  if (addressResult.is_active) {
230
- score += 10
305
+ score += 10;
231
306
  }
232
307
  if (!addressResult.is_commercial) {
233
- score += 5
308
+ score += 5;
234
309
  }
235
310
  }
236
311
 
237
- return Math.min(score, 100)
312
+ return Math.min(score, 100);
238
313
  }
239
314
 
240
- function generateFakeAddresses(count) {
315
+ function generateFakeAddresses(count: number): string[] {
241
316
  const fakeAddresses = [
242
- '123 Fake St, Nowhere, CA',
243
- '456 Made Up Ave, Fictional, TX',
244
- '789 Pretend Dr, Imaginary, FL',
245
- '101 False Blvd, Bogus, NY',
246
- '202 Phony Way, Unreal, WA'
247
- ]
248
- return fakeAddresses.sort(() => Math.random() - 0.5).slice(0, count)
317
+ "123 Fake St, Nowhere, CA",
318
+ "456 Made Up Ave, Fictional, TX",
319
+ "789 Pretend Dr, Imaginary, FL",
320
+ "101 False Blvd, Bogus, NY",
321
+ "202 Phony Way, Unreal, WA",
322
+ ];
323
+ return fakeAddresses.sort(() => Math.random() - 0.5).slice(0, count);
249
324
  }
250
325
 
251
- function generateFakePhones(count) {
252
- const fakePhones = []
326
+ function generateFakePhones(count: number): string[] {
327
+ const fakePhones: string[] = [];
253
328
  for (let i = 0; i < count; i++) {
254
- const areaCode = Math.floor(Math.random() * 900) + 100
255
- const exchange = Math.floor(Math.random() * 900) + 100
256
- const number = Math.floor(Math.random() * 9000) + 1000
257
- fakePhones.push(`${areaCode}${exchange}${number}`)
329
+ const areaCode = Math.floor(Math.random() * 900) + 100;
330
+ const exchange = Math.floor(Math.random() * 900) + 100;
331
+ const number = Math.floor(Math.random() * 9000) + 1000;
332
+ fakePhones.push(`${areaCode}${exchange}${number}`);
258
333
  }
259
- return fakePhones
334
+ return fakePhones;
335
+ }
336
+
337
+ interface HistoricalData {
338
+ previous_addresses: string[];
339
+ previous_phones: string[];
340
+ associated_names: string[];
341
+ }
342
+
343
+ interface VerificationQuestion {
344
+ id: string;
345
+ question: string;
346
+ type: "address_history" | "phone_history" | "name_verification";
347
+ options?: string[];
260
348
  }
261
349
 
262
- function generateVerificationQuestions(historicalData, inputName) {
263
- const questions = []
350
+ function generateVerificationQuestions(historicalData: HistoricalData, inputName: string): VerificationQuestion[] {
351
+ const questions: VerificationQuestion[] = [];
264
352
 
265
353
  // Address history questions
266
354
  if (historicalData.previous_addresses.length > 0) {
267
- const shuffledAddresses = [...historicalData.previous_addresses].sort(() => Math.random() - 0.5)
268
- const realAddresses = shuffledAddresses.slice(0, 3)
269
- const fakeAddresses = generateFakeAddresses(2)
270
- const allOptions = [...realAddresses, ...fakeAddresses].sort(() => Math.random() - 0.5)
355
+ const shuffledAddresses = [...historicalData.previous_addresses].sort(
356
+ () => Math.random() - 0.5,
357
+ );
358
+ const realAddresses = shuffledAddresses.slice(0, 3);
359
+ const fakeAddresses = generateFakeAddresses(2);
360
+ const allOptions = [...realAddresses, ...fakeAddresses].sort(
361
+ () => Math.random() - 0.5,
362
+ );
271
363
 
272
364
  questions.push({
273
365
  id: `addr_${Date.now()}`,
274
366
  question: `Which of the following addresses have you lived at in the past?`,
275
- type: 'address_history',
276
- options: allOptions
277
- })
367
+ type: "address_history",
368
+ options: allOptions,
369
+ });
278
370
  }
279
371
 
280
372
  // Phone history questions
281
373
  if (historicalData.previous_phones.length > 1) {
282
- const shuffledPhones = [...historicalData.previous_phones].sort(() => Math.random() - 0.5)
283
- const realPhones = shuffledPhones.slice(0, 2)
284
- const fakePhones = generateFakePhones(2)
285
- const allOptions = [...realPhones, ...fakePhones, 'None of the above'].sort(() => Math.random() - 0.5)
374
+ const shuffledPhones = [...historicalData.previous_phones].sort(
375
+ () => Math.random() - 0.5,
376
+ );
377
+ const realPhones = shuffledPhones.slice(0, 2);
378
+ const fakePhones = generateFakePhones(2);
379
+ const allOptions = [...realPhones, ...fakePhones, "None of the above"].sort(
380
+ () => Math.random() - 0.5,
381
+ );
286
382
 
287
383
  questions.push({
288
384
  id: `phone_${Date.now()}`,
289
385
  question: `Which of the following phone numbers have you previously used?`,
290
- type: 'phone_history',
291
- options: allOptions
292
- })
386
+ type: "phone_history",
387
+ options: allOptions,
388
+ });
293
389
  }
294
390
 
295
391
  // Name verification questions
296
392
  if (historicalData.associated_names.length > 0) {
297
393
  const otherNames = historicalData.associated_names.filter(
298
- name => normalizeName(name) !== normalizeName(inputName)
299
- )
300
-
394
+ (name: string) => normalizeName(name) !== normalizeName(inputName),
395
+ );
396
+
301
397
  if (otherNames.length > 0) {
302
398
  questions.push({
303
399
  id: `name_${Date.now()}`,
304
400
  question: `Are any of the following names associated with you (maiden name, nickname, etc.)?`,
305
- type: 'name_verification',
306
- options: [...otherNames.slice(0, 4), 'None of the above']
307
- })
401
+ type: "name_verification",
402
+ options: [...otherNames.slice(0, 4), "None of the above"],
403
+ });
308
404
  }
309
405
  }
310
406
 
311
- return questions
407
+ return questions;
312
408
  }
313
409
 
314
- function generateRecommendations(score, phoneResult, personResult, addressResult) {
315
- const recommendations = []
410
+ function generateRecommendations(
411
+ score: number,
412
+ phoneResult: any,
413
+ personResult: any,
414
+ addressResult: any,
415
+ ): string[] {
416
+ const recommendations: string[] = [];
316
417
 
317
418
  if (score < 30) {
318
- recommendations.push('Consider requesting additional identification documents')
319
- recommendations.push('Verify identity through alternative methods (government ID, utility bills)')
419
+ recommendations.push(
420
+ "Consider requesting additional identification documents",
421
+ );
422
+ recommendations.push(
423
+ "Verify identity through alternative methods (government ID, utility bills)",
424
+ );
320
425
  } else if (score < 60) {
321
- recommendations.push('Request additional verification questions')
322
- recommendations.push('Consider manual review of provided information')
426
+ recommendations.push("Request additional verification questions");
427
+ recommendations.push("Consider manual review of provided information");
323
428
  } else if (score < 80) {
324
- recommendations.push('Proceed with standard verification process')
429
+ recommendations.push("Proceed with standard verification process");
325
430
  } else {
326
- recommendations.push('High confidence verification - proceed with confidence')
431
+ recommendations.push(
432
+ "High confidence verification - proceed with confidence",
433
+ );
327
434
  }
328
435
 
329
436
  if (!phoneResult?.is_valid) {
330
- recommendations.push('Request alternative contact phone number')
437
+ recommendations.push("Request alternative contact phone number");
331
438
  }
332
439
 
333
440
  if (!addressResult?.is_valid) {
334
- recommendations.push('Verify current address with utility bill or bank statement')
441
+ recommendations.push(
442
+ "Verify current address with utility bill or bank statement",
443
+ );
335
444
  }
336
445
 
337
- if (phoneResult?.line_type === 'NonFixedVOIP') {
338
- recommendations.push('VOIP number detected - consider additional verification')
446
+ if (phoneResult?.line_type === "NonFixedVOIP") {
447
+ recommendations.push(
448
+ "VOIP number detected - consider additional verification",
449
+ );
339
450
  }
340
451
 
341
- return recommendations
452
+ return recommendations;
342
453
  }
343
454
 
344
455
  // Main Verification Function
345
- async function verifyIdentity(input, apiKey) {
346
- const { phone_number, legal_name, current_address } = input
347
-
456
+ async function verifyIdentity(input: PersonInput, apiKey: string) {
457
+ const { phone_number, legal_name, current_address } = input;
458
+
348
459
  // Run all API calls in parallel
349
460
  const [phoneData, personData, addressData] = await Promise.allSettled([
350
461
  reversePhone(phone_number, apiKey, {
351
462
  name: legal_name,
352
- postalCode: current_address.postal_code
463
+ postalCode: current_address.postal_code,
353
464
  }),
354
465
  findPerson(legal_name, current_address, apiKey),
355
- reverseAddress(current_address, apiKey)
356
- ])
466
+ reverseAddress(current_address, apiKey),
467
+ ]);
357
468
 
358
469
  // Process results
359
- const phoneResult = phoneData.status === 'fulfilled' ? phoneData.value : null
360
- const personResult = personData.status === 'fulfilled' ? personData.value : null
361
- const addressResult = addressData.status === 'fulfilled' ? addressData.value : null
470
+ const phoneResult = phoneData.status === "fulfilled" ? phoneData.value : null;
471
+ const personResult =
472
+ personData.status === "fulfilled" ? personData.value : null;
473
+ const addressResult =
474
+ addressData.status === "fulfilled" ? addressData.value : null;
362
475
 
363
476
  // Extract historical data
364
- const historicalData = extractHistoricalData(phoneResult, personResult, addressResult)
365
-
477
+ const historicalData = extractHistoricalData(
478
+ phoneResult,
479
+ personResult,
480
+ addressResult,
481
+ );
482
+
366
483
  // Calculate verification score
367
484
  const verificationScore = calculateVerificationScore(
368
485
  phoneResult,
369
486
  personResult,
370
487
  addressResult,
371
- input
372
- )
488
+ input,
489
+ );
373
490
 
374
491
  // Generate verification questions
375
- const questions = generateVerificationQuestions(historicalData, legal_name)
492
+ const questions = generateVerificationQuestions(historicalData, legal_name);
376
493
 
377
494
  // Generate recommendations
378
495
  const recommendations = generateRecommendations(
379
496
  verificationScore,
380
497
  phoneResult,
381
498
  personResult,
382
- addressResult
383
- )
499
+ addressResult,
500
+ );
384
501
 
385
502
  return {
386
503
  verification_score: verificationScore,
@@ -389,99 +506,104 @@ async function verifyIdentity(input, apiKey) {
389
506
  address_validated: addressResult?.is_valid || false,
390
507
  questions,
391
508
  historical_data: historicalData,
392
- recommendations
393
- }
509
+ recommendations,
510
+ };
394
511
  }
395
512
 
396
513
  // Create Hono app
397
- const app = new OpenAPIHono()
514
+ const app = new OpenAPIHono();
398
515
 
399
516
  // Middleware
400
- app.use('*', cors())
401
- app.use('*', logger())
517
+ app.use("*", cors());
518
+ app.use("*", logger());
402
519
 
403
520
  // Get API key from environment
404
521
  const getApiKey = () => {
405
- const apiKey = process.env.TRESTLE_API_KEY
522
+ const apiKey = process.env.TRESTLE_API_KEY;
406
523
  if (!apiKey) {
407
- throw new Error('TRESTLE_API_KEY environment variable is required')
524
+ throw new Error("TRESTLE_API_KEY environment variable is required");
408
525
  }
409
- return apiKey
410
- }
526
+ return apiKey;
527
+ };
411
528
 
412
529
  // Main API route
413
530
  const verifyIdentityRoute = createRoute({
414
- method: 'post',
415
- path: '/verify-identity',
416
- tags: ['Identity Verification'],
417
- summary: 'Verify person identity',
418
- description: 'Verify a person\'s identity using phone number, legal name, and current address',
531
+ method: "post",
532
+ path: "/verify-identity",
533
+ tags: ["Identity Verification"],
534
+ summary: "Verify person identity",
535
+ description:
536
+ "Verify a person's identity using phone number, legal name, and current address",
419
537
  request: {
420
538
  body: {
421
539
  content: {
422
- 'application/json': {
423
- schema: PersonInputSchema
424
- }
425
- }
426
- }
540
+ "application/json": {
541
+ schema: PersonInputSchema,
542
+ },
543
+ },
544
+ },
427
545
  },
428
546
  responses: {
429
547
  200: {
430
548
  content: {
431
- 'application/json': {
432
- schema: VerificationResponseSchema
433
- }
549
+ "application/json": {
550
+ schema: VerificationResponseSchema,
551
+ },
434
552
  },
435
- description: 'Identity verification completed successfully'
553
+ description: "Identity verification completed successfully",
436
554
  },
437
555
  400: {
438
- description: 'Bad request - invalid input data'
556
+ description: "Bad request - invalid input data",
439
557
  },
440
558
  500: {
441
- description: 'Internal server error'
442
- }
443
- }
444
- })
559
+ description: "Internal server error",
560
+ },
561
+ },
562
+ });
445
563
 
446
564
  app.openapi(verifyIdentityRoute, async (c) => {
447
565
  try {
448
- const body = c.req.valid('json')
449
- const apiKey = getApiKey()
450
- const result = await verifyIdentity(body, apiKey)
451
-
452
- return c.json(result, 200)
566
+ const body = c.req.valid("json") as PersonInput;
567
+ const apiKey = getApiKey();
568
+ const result = await verifyIdentity(body, apiKey);
569
+
570
+ return c.json(result, 200);
453
571
  } catch (error) {
454
- console.error('Identity verification error:', error)
455
- return c.json({
456
- error: 'Failed to verify identity',
457
- message: error instanceof Error ? error.message : 'Unknown error'
458
- }, 500)
572
+ console.error("Identity verification error:", error);
573
+ return c.json(
574
+ {
575
+ error: "Failed to verify identity",
576
+ message: error instanceof Error ? error.message : "Unknown error",
577
+ },
578
+ 500,
579
+ );
459
580
  }
460
- })
581
+ });
461
582
 
462
583
  // Health check endpoint
463
- app.get('/health', (c) => {
464
- return c.json({ status: 'healthy', timestamp: new Date().toISOString() })
465
- })
584
+ app.get("/health", (c) => {
585
+ return c.json({ status: "healthy", timestamp: new Date().toISOString() });
586
+ });
466
587
 
467
588
  // OpenAPI documentation
468
- app.doc('/doc', {
469
- openapi: '3.0.0',
589
+ app.doc("/doc", {
590
+ openapi: "3.0.0",
470
591
  info: {
471
- version: '1.0.0',
472
- title: 'Identity Verification API',
473
- description: 'API for verifying person identity using TrestleIQ data services'
592
+ version: "1.0.0",
593
+ title: "Identity Verification API",
594
+ description:
595
+ "API for verifying person identity using TrestleIQ data services",
474
596
  },
475
597
  servers: [
476
598
  {
477
- url: 'http://localhost:3000',
478
- description: 'Development server'
479
- }
480
- ]
481
- })
599
+ url: "http://localhost:3000",
600
+ description: "Development server",
601
+ },
602
+ ],
603
+ });
482
604
 
483
605
  // Serve Swagger UI
484
- app.get('/swagger', async (c) => {
606
+ app.get("/swagger", async (c) => {
485
607
  return c.html(`
486
608
  <!DOCTYPE html>
487
609
  <html>
@@ -501,11 +623,11 @@ app.get('/swagger', async (c) => {
501
623
  </script>
502
624
  </body>
503
625
  </html>
504
- `)
505
- })
626
+ `);
627
+ });
506
628
 
507
629
  // Demo endpoint with sample data
508
- app.get('/demo', async (c) => {
630
+ app.get("/demo", async (c) => {
509
631
  const sampleRequest = {
510
632
  phone_number: "2069735100",
511
633
  legal_name: "John Smith",
@@ -514,40 +636,42 @@ app.get('/demo', async (c) => {
514
636
  city: "Seattle",
515
637
  state_code: "WA",
516
638
  postal_code: "98101",
517
- country_code: "US"
518
- }
519
- }
639
+ country_code: "US",
640
+ },
641
+ };
520
642
 
521
643
  try {
522
- const apiKey = getApiKey()
523
- const result = await verifyIdentity(sampleRequest, apiKey)
524
-
644
+ const apiKey = getApiKey();
645
+ const result = await verifyIdentity(sampleRequest, apiKey);
646
+
525
647
  return c.json({
526
648
  demo_request: sampleRequest,
527
649
  demo_response: result,
528
- note: "This is a demo using sample data. Use POST /verify-identity for real verification."
529
- })
650
+ note: "This is a demo using sample data. Use POST /verify-identity for real verification.",
651
+ });
530
652
  } catch (error) {
531
653
  return c.json({
532
654
  demo_request: sampleRequest,
533
655
  error: "Demo failed - check your TRESTLE_API_KEY",
534
- note: "This demo requires a valid TrestleIQ API key"
535
- })
656
+ note: "This demo requires a valid TrestleIQ API key",
657
+ });
536
658
  }
537
- })
659
+ });
538
660
 
539
- export default app
661
+ export default app;
662
+
663
+ declare const Bun: { serve: (options: { port: number; fetch: any }) => unknown };
540
664
 
541
665
  // Start server if not imported as module
542
- if (import.meta.main) {
543
- const port = parseInt(process.env.PORT || '3000')
544
- console.log(`🚀 Server running at http://localhost:${port}`)
545
- console.log(`📚 API Documentation: http://localhost:${port}/swagger`)
546
- console.log(`🔍 OpenAPI Spec: http://localhost:${port}/doc`)
547
- console.log(`🎮 Demo Endpoint: http://localhost:${port}/demo`)
548
-
666
+ if ((import.meta as any).main) {
667
+ const port = parseInt(process.env.PORT || "3000");
668
+ console.log(`🚀 Server running at http://localhost:${port}`);
669
+ console.log(`📚 API Documentation: http://localhost:${port}/swagger`);
670
+ console.log(`🔍 OpenAPI Spec: http://localhost:${port}/doc`);
671
+ console.log(`🎮 Demo Endpoint: http://localhost:${port}/demo`);
672
+
549
673
  Bun.serve({
550
674
  port,
551
675
  fetch: app.fetch,
552
- })
553
- }
676
+ });
677
+ }