@securelend/sdk 0.1.0-beta.5 → 0.2.0-beta.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 +18 -0
- package/dist/index.d.mts +142 -4
- package/dist/index.d.ts +142 -4
- package/dist/index.js +1096 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1066 -1
- package/dist/index.mjs.map +1 -1
- package/dist/types/index.d.mts +1424 -205
- package/dist/types/index.d.ts +1424 -205
- package/dist/types/index.js +214 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/index.mjs +192 -1
- package/dist/types/index.mjs.map +1 -1
- package/package.json +2 -3
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
3
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
|
+
import { createHash, createHmac, timingSafeEqual } from 'crypto';
|
|
4
5
|
|
|
5
6
|
// src/client.ts
|
|
6
7
|
|
|
@@ -51,6 +52,30 @@ var ServerError = class extends SecureLendError {
|
|
|
51
52
|
this.name = "ServerError";
|
|
52
53
|
}
|
|
53
54
|
};
|
|
55
|
+
var FeatureDisabledError = class extends SecureLendError {
|
|
56
|
+
constructor(message = "This feature is off for this workspace.", details) {
|
|
57
|
+
super(message, "feature_disabled", details);
|
|
58
|
+
this.name = "FeatureDisabledError";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var ConsentRequiredError = class extends SecureLendError {
|
|
62
|
+
constructor(message = "A live consent is required before this package can be read.", details) {
|
|
63
|
+
super(message, "consent_required", details);
|
|
64
|
+
this.name = "ConsentRequiredError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var InvalidTransitionError = class extends SecureLendError {
|
|
68
|
+
constructor(message = "That outcome is not available.", details) {
|
|
69
|
+
super(message, "invalid_transition", details);
|
|
70
|
+
this.name = "InvalidTransitionError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var WebhookSignatureError = class extends SecureLendError {
|
|
74
|
+
constructor(message = "Webhook signature did not match.", details) {
|
|
75
|
+
super(message, "webhook_signature", details);
|
|
76
|
+
this.name = "WebhookSignatureError";
|
|
77
|
+
}
|
|
78
|
+
};
|
|
54
79
|
|
|
55
80
|
// src/utils/mcp.ts
|
|
56
81
|
var MCPClient = class {
|
|
@@ -125,6 +150,197 @@ var MCPClient = class {
|
|
|
125
150
|
this.debug = false;
|
|
126
151
|
}
|
|
127
152
|
};
|
|
153
|
+
var OUTCOME_STATES = [
|
|
154
|
+
"viewed",
|
|
155
|
+
"info_requested",
|
|
156
|
+
"declined",
|
|
157
|
+
"term_sheet",
|
|
158
|
+
"approved",
|
|
159
|
+
"funded",
|
|
160
|
+
"withdrawn"
|
|
161
|
+
];
|
|
162
|
+
var DECLINE_REASON_CODES = [
|
|
163
|
+
"outside_credit_box",
|
|
164
|
+
"industry",
|
|
165
|
+
"time_in_business",
|
|
166
|
+
"dscr",
|
|
167
|
+
"collateral",
|
|
168
|
+
"amount",
|
|
169
|
+
"state",
|
|
170
|
+
"credit_score",
|
|
171
|
+
"docs_incomplete",
|
|
172
|
+
"capacity",
|
|
173
|
+
"other"
|
|
174
|
+
];
|
|
175
|
+
var SHARE_SCOPES = ["summary", "memo", "checks", "documents"];
|
|
176
|
+
var FEEDBACK_TYPES = [
|
|
177
|
+
"rating",
|
|
178
|
+
"edit",
|
|
179
|
+
"comment",
|
|
180
|
+
"approve",
|
|
181
|
+
"reject",
|
|
182
|
+
"regenerate"
|
|
183
|
+
];
|
|
184
|
+
var FEEDBACK_TARGETS = [
|
|
185
|
+
"memo_section",
|
|
186
|
+
"memo",
|
|
187
|
+
"tool",
|
|
188
|
+
"decision",
|
|
189
|
+
"lead_match"
|
|
190
|
+
];
|
|
191
|
+
var EVENT_TYPES = [
|
|
192
|
+
"lead.created",
|
|
193
|
+
"lead.matched",
|
|
194
|
+
"lead.notified",
|
|
195
|
+
"package.shared",
|
|
196
|
+
"package.opened",
|
|
197
|
+
"consent.revoked",
|
|
198
|
+
"outcome.recorded",
|
|
199
|
+
"credit_box.updated",
|
|
200
|
+
"feedback.recorded"
|
|
201
|
+
];
|
|
202
|
+
var leadSchema = z.object({
|
|
203
|
+
id: z.string(),
|
|
204
|
+
tenantId: z.string().nullable().optional(),
|
|
205
|
+
status: z.string().nullable().optional(),
|
|
206
|
+
productType: z.string().nullable().optional(),
|
|
207
|
+
amount: z.number().nullable().optional(),
|
|
208
|
+
legalName: z.string().nullable().optional(),
|
|
209
|
+
email: z.string().nullable().optional(),
|
|
210
|
+
caseId: z.string().nullable().optional(),
|
|
211
|
+
lenders: z.array(
|
|
212
|
+
z.object({
|
|
213
|
+
id: z.string().nullable().optional(),
|
|
214
|
+
name: z.string().optional(),
|
|
215
|
+
tenantId: z.string().nullable().optional()
|
|
216
|
+
}).passthrough()
|
|
217
|
+
).optional(),
|
|
218
|
+
createdAt: z.string().optional(),
|
|
219
|
+
updatedAt: z.string().optional()
|
|
220
|
+
}).passthrough();
|
|
221
|
+
var leadListSchema = z.object({
|
|
222
|
+
items: z.array(leadSchema),
|
|
223
|
+
tenantId: z.string().optional()
|
|
224
|
+
}).passthrough();
|
|
225
|
+
var uploadResultSchema = z.object({
|
|
226
|
+
key: z.string().optional(),
|
|
227
|
+
filename: z.string().optional(),
|
|
228
|
+
documentType: z.string().optional(),
|
|
229
|
+
applicationId: z.string().optional()
|
|
230
|
+
}).passthrough();
|
|
231
|
+
var matchComponentsSchema = z.object({
|
|
232
|
+
rules: z.number().nullable().optional(),
|
|
233
|
+
fit: z.number().nullable().optional(),
|
|
234
|
+
outcome: z.number().nullable().optional(),
|
|
235
|
+
sba: z.number().nullable().optional(),
|
|
236
|
+
samples: z.number().nullable().optional()
|
|
237
|
+
}).passthrough();
|
|
238
|
+
var lenderMatchSchema = z.object({
|
|
239
|
+
lenderId: z.string().nullable().optional(),
|
|
240
|
+
score: z.number().nullable().optional(),
|
|
241
|
+
components: matchComponentsSchema.nullable().optional(),
|
|
242
|
+
generationId: z.string().nullable().optional()
|
|
243
|
+
}).passthrough();
|
|
244
|
+
var matchListSchema = z.object({
|
|
245
|
+
leadId: z.string(),
|
|
246
|
+
generationId: z.string().nullable().optional(),
|
|
247
|
+
lenders: z.array(lenderMatchSchema)
|
|
248
|
+
}).passthrough();
|
|
249
|
+
var packageSchema = z.object({
|
|
250
|
+
packageId: z.string().optional(),
|
|
251
|
+
caseId: z.string().optional(),
|
|
252
|
+
id: z.string().optional(),
|
|
253
|
+
generationId: z.string().nullable().optional(),
|
|
254
|
+
auth: z.string().optional(),
|
|
255
|
+
error: z.string().optional(),
|
|
256
|
+
deskUrl: z.string().optional()
|
|
257
|
+
}).passthrough();
|
|
258
|
+
var shareConsentSchema = z.object({
|
|
259
|
+
id: z.string(),
|
|
260
|
+
leadId: z.string(),
|
|
261
|
+
packageId: z.string(),
|
|
262
|
+
packageVersion: z.number(),
|
|
263
|
+
lenderId: z.string().optional(),
|
|
264
|
+
scope: z.array(z.string()),
|
|
265
|
+
expiresAt: z.string(),
|
|
266
|
+
revokedAt: z.string().nullable().optional(),
|
|
267
|
+
borrowerRef: z.string().optional(),
|
|
268
|
+
grantedAt: z.string().optional()
|
|
269
|
+
}).passthrough();
|
|
270
|
+
var shareReceiptSchema = z.object({
|
|
271
|
+
id: z.string(),
|
|
272
|
+
consentId: z.string(),
|
|
273
|
+
event: z.string(),
|
|
274
|
+
createdAt: z.string(),
|
|
275
|
+
lenderId: z.string().optional()
|
|
276
|
+
}).passthrough();
|
|
277
|
+
var leadOutcomeSchema = z.object({
|
|
278
|
+
id: z.string(),
|
|
279
|
+
leadId: z.string(),
|
|
280
|
+
lenderId: z.string(),
|
|
281
|
+
state: z.string(),
|
|
282
|
+
reasonCode: z.string().nullable().optional(),
|
|
283
|
+
reasonText: z.string().nullable().optional(),
|
|
284
|
+
fundedAmount: z.number().nullable().optional(),
|
|
285
|
+
fundedOn: z.string().nullable().optional(),
|
|
286
|
+
surface: z.string().optional(),
|
|
287
|
+
feedbackId: z.string().nullable().optional(),
|
|
288
|
+
matchGenerationId: z.string().nullable().optional(),
|
|
289
|
+
createdAt: z.string().optional()
|
|
290
|
+
}).passthrough();
|
|
291
|
+
var creditBoxSchema = z.object({
|
|
292
|
+
version: z.number(),
|
|
293
|
+
lenderId: z.string().optional(),
|
|
294
|
+
criteria: z.record(z.unknown()).optional(),
|
|
295
|
+
capacityPerWeek: z.number().nullable().optional(),
|
|
296
|
+
status: z.string(),
|
|
297
|
+
pausedUntil: z.string().nullable().optional(),
|
|
298
|
+
createdAt: z.string().optional()
|
|
299
|
+
}).passthrough();
|
|
300
|
+
var creditBoxViewSchema = z.object({
|
|
301
|
+
current: creditBoxSchema.nullable(),
|
|
302
|
+
versions: z.array(creditBoxSchema)
|
|
303
|
+
}).passthrough();
|
|
304
|
+
var feedbackEventSchema = z.object({
|
|
305
|
+
id: z.string(),
|
|
306
|
+
generationId: z.string().nullable().optional(),
|
|
307
|
+
caseId: z.string().nullable().optional(),
|
|
308
|
+
surface: z.string().optional(),
|
|
309
|
+
type: z.string(),
|
|
310
|
+
rating: z.number().nullable().optional(),
|
|
311
|
+
comment: z.string().nullable().optional(),
|
|
312
|
+
targetKind: z.string().optional(),
|
|
313
|
+
targetKey: z.string().optional(),
|
|
314
|
+
createdAt: z.string().optional()
|
|
315
|
+
}).passthrough();
|
|
316
|
+
var feedbackCreatedSchema = z.object({
|
|
317
|
+
id: z.string()
|
|
318
|
+
}).passthrough();
|
|
319
|
+
var liquidityCellSchema = z.object({
|
|
320
|
+
key: z.string(),
|
|
321
|
+
leads: z.number(),
|
|
322
|
+
matchRate: z.number().nullable().optional(),
|
|
323
|
+
responseRate48h: z.number().nullable().optional(),
|
|
324
|
+
medianResponseMs: z.number().nullable().optional(),
|
|
325
|
+
termSheetRate: z.number().nullable().optional(),
|
|
326
|
+
fundedRate: z.number().nullable().optional(),
|
|
327
|
+
fundedAmount: z.number().nullable().optional(),
|
|
328
|
+
lenderCount: z.number().optional()
|
|
329
|
+
}).passthrough();
|
|
330
|
+
var liquidityReportSchema = z.object({
|
|
331
|
+
view: z.string(),
|
|
332
|
+
groupBy: z.string(),
|
|
333
|
+
cells: z.array(liquidityCellSchema)
|
|
334
|
+
}).passthrough();
|
|
335
|
+
var secureLendEventSchema = z.object({
|
|
336
|
+
type: z.string(),
|
|
337
|
+
id: z.string(),
|
|
338
|
+
createdAt: z.string(),
|
|
339
|
+
tenantId: z.string(),
|
|
340
|
+
data: z.record(z.unknown()).optional()
|
|
341
|
+
}).passthrough();
|
|
342
|
+
|
|
343
|
+
// src/types/index.ts
|
|
128
344
|
var anyObjectSchema = z.object({}).passthrough();
|
|
129
345
|
var CreditTier = /* @__PURE__ */ ((CreditTier2) => {
|
|
130
346
|
CreditTier2["PRIME"] = "PRIME";
|
|
@@ -695,6 +911,834 @@ var leaseVsPurchaseResponseSchema = z.object({
|
|
|
695
911
|
}),
|
|
696
912
|
widget: z.string().optional()
|
|
697
913
|
});
|
|
914
|
+
var CONSENT_CODES = /* @__PURE__ */ new Set([
|
|
915
|
+
"consent_required",
|
|
916
|
+
"consent_revoked",
|
|
917
|
+
"consent_expired"
|
|
918
|
+
]);
|
|
919
|
+
function sleep(ms) {
|
|
920
|
+
if (ms <= 0) return Promise.resolve();
|
|
921
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
922
|
+
}
|
|
923
|
+
function newIdempotencyKey() {
|
|
924
|
+
const cryptoRef = globalThis.crypto;
|
|
925
|
+
if (cryptoRef && typeof cryptoRef.randomUUID === "function") {
|
|
926
|
+
return cryptoRef.randomUUID();
|
|
927
|
+
}
|
|
928
|
+
return `idem_${Date.now().toString(16)}_${Math.random().toString(16).slice(2)}`;
|
|
929
|
+
}
|
|
930
|
+
function bearer(apiKey) {
|
|
931
|
+
if (!apiKey) return void 0;
|
|
932
|
+
const trimmed = apiKey.trim();
|
|
933
|
+
if (!trimmed) return void 0;
|
|
934
|
+
return trimmed.toLowerCase().startsWith("bearer ") ? trimmed : `Bearer ${trimmed}`;
|
|
935
|
+
}
|
|
936
|
+
function retryDelay(response, fallback) {
|
|
937
|
+
const header = response.headers.get("retry-after");
|
|
938
|
+
if (header && /^\d+$/.test(header)) {
|
|
939
|
+
return Math.min(Number(header) * 1e3, 2e3);
|
|
940
|
+
}
|
|
941
|
+
return fallback;
|
|
942
|
+
}
|
|
943
|
+
function validationMessage(code) {
|
|
944
|
+
if (code === "reason_required") return "A decline needs a reason code.";
|
|
945
|
+
if (code === "funded_amount_required") return "Funded needs an amount.";
|
|
946
|
+
if (code === "funded_date_required") {
|
|
947
|
+
return "Funded needs a date as YYYY-MM-DD.";
|
|
948
|
+
}
|
|
949
|
+
if (code === "confirmation_required") {
|
|
950
|
+
return "The lead needs confirmation before it is created.";
|
|
951
|
+
}
|
|
952
|
+
return "The request was not accepted.";
|
|
953
|
+
}
|
|
954
|
+
var RestClient = class {
|
|
955
|
+
constructor(options) {
|
|
956
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
957
|
+
this.apiKey = options.apiKey;
|
|
958
|
+
this.fetchImpl = options.fetchImpl;
|
|
959
|
+
this.maxRetries = options.maxRetries ?? 2;
|
|
960
|
+
this.retryDelayMs = options.retryDelayMs ?? 0;
|
|
961
|
+
}
|
|
962
|
+
setApiKey(apiKey) {
|
|
963
|
+
this.apiKey = apiKey;
|
|
964
|
+
}
|
|
965
|
+
async send(request) {
|
|
966
|
+
const retries = request.form ? 0 : this.maxRetries;
|
|
967
|
+
const url = this.url(request.path, request.query);
|
|
968
|
+
const init = this.init(request);
|
|
969
|
+
let lastError = null;
|
|
970
|
+
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
|
971
|
+
let response;
|
|
972
|
+
try {
|
|
973
|
+
response = await this.fetcher()(url, init);
|
|
974
|
+
} catch (err) {
|
|
975
|
+
const detail = err instanceof Error && err.message ? err.message.replace(/\s+/g, " ").slice(0, 180) : "";
|
|
976
|
+
lastError = new NetworkError(
|
|
977
|
+
detail ? `The request did not reach the server. ${detail}` : "The request did not reach the server."
|
|
978
|
+
);
|
|
979
|
+
if (attempt < retries) {
|
|
980
|
+
await sleep(this.retryDelayMs);
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
throw lastError;
|
|
984
|
+
}
|
|
985
|
+
if ((response.status === 429 || response.status >= 500) && attempt < retries) {
|
|
986
|
+
await sleep(retryDelay(response, this.retryDelayMs));
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
return this.read(response, request);
|
|
990
|
+
}
|
|
991
|
+
throw lastError || new NetworkError("The request did not reach the server.");
|
|
992
|
+
}
|
|
993
|
+
fetcher() {
|
|
994
|
+
if (this.fetchImpl) return this.fetchImpl;
|
|
995
|
+
if (typeof globalThis.fetch === "function") {
|
|
996
|
+
return globalThis.fetch.bind(globalThis);
|
|
997
|
+
}
|
|
998
|
+
throw new NetworkError("This runtime has no fetch implementation.");
|
|
999
|
+
}
|
|
1000
|
+
url(path, query) {
|
|
1001
|
+
const suffix = path.startsWith("/") ? path : `/${path}`;
|
|
1002
|
+
const params = new URLSearchParams();
|
|
1003
|
+
for (const [key, value] of Object.entries(query || {})) {
|
|
1004
|
+
if (value == null || value === "") continue;
|
|
1005
|
+
params.set(key, String(value));
|
|
1006
|
+
}
|
|
1007
|
+
const qs = params.toString();
|
|
1008
|
+
return `${this.baseUrl}${suffix}${qs ? `?${qs}` : ""}`;
|
|
1009
|
+
}
|
|
1010
|
+
init(request) {
|
|
1011
|
+
const headers = { accept: "application/json" };
|
|
1012
|
+
const authorization = bearer(this.apiKey);
|
|
1013
|
+
if (authorization) headers.authorization = authorization;
|
|
1014
|
+
const method = request.method.toUpperCase();
|
|
1015
|
+
if (method === "POST" || method === "PUT" || method === "PATCH") {
|
|
1016
|
+
headers["idempotency-key"] = request.idempotencyKey || newIdempotencyKey();
|
|
1017
|
+
}
|
|
1018
|
+
if (request.form) {
|
|
1019
|
+
return { method, headers, body: request.form };
|
|
1020
|
+
}
|
|
1021
|
+
if (request.body !== void 0) {
|
|
1022
|
+
headers["content-type"] = "application/json";
|
|
1023
|
+
return { method, headers, body: JSON.stringify(request.body) };
|
|
1024
|
+
}
|
|
1025
|
+
return { method, headers };
|
|
1026
|
+
}
|
|
1027
|
+
async read(response, request) {
|
|
1028
|
+
const text = await response.text();
|
|
1029
|
+
let payload = null;
|
|
1030
|
+
if (text) {
|
|
1031
|
+
try {
|
|
1032
|
+
payload = JSON.parse(text);
|
|
1033
|
+
} catch {
|
|
1034
|
+
payload = null;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
if (response.status >= 200 && response.status < 300) {
|
|
1038
|
+
try {
|
|
1039
|
+
return request.schema.parse(payload ?? {});
|
|
1040
|
+
} catch (err) {
|
|
1041
|
+
if (err instanceof z.ZodError) {
|
|
1042
|
+
throw new ValidationError(
|
|
1043
|
+
"The server response did not match the expected shape.",
|
|
1044
|
+
{ issues: err.issues.map((issue) => issue.path.join(".")) }
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
throw err;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
const code = String(payload?.error || "");
|
|
1051
|
+
const details = { status: response.status, error: code || void 0 };
|
|
1052
|
+
if (response.status === 401 || code === "invalid_signature") {
|
|
1053
|
+
throw new AuthenticationError("The API key was not accepted.");
|
|
1054
|
+
}
|
|
1055
|
+
if (response.status === 429) {
|
|
1056
|
+
const retryAfter = response.headers.get("retry-after");
|
|
1057
|
+
throw new RateLimitError(
|
|
1058
|
+
"The request was rate limited.",
|
|
1059
|
+
retryAfter && /^\d+$/.test(retryAfter) ? Number(retryAfter) : void 0
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
if (CONSENT_CODES.has(code)) {
|
|
1063
|
+
const message = code === "consent_revoked" ? "This consent was revoked." : code === "consent_expired" ? "This consent has expired." : "A live consent is required before this package can be read.";
|
|
1064
|
+
throw new ConsentRequiredError(message, details);
|
|
1065
|
+
}
|
|
1066
|
+
if (code === "invalid_transition") {
|
|
1067
|
+
throw new InvalidTransitionError(
|
|
1068
|
+
"That outcome is not available.",
|
|
1069
|
+
details
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
if (request.featureGated && (response.status === 404 || response.status === 403 && (code === "not_found" || code === "feature_disabled"))) {
|
|
1073
|
+
throw new FeatureDisabledError(
|
|
1074
|
+
"This feature is off for this workspace.",
|
|
1075
|
+
details
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
if (response.status === 404) {
|
|
1079
|
+
throw new NotFoundError("The record was not found.");
|
|
1080
|
+
}
|
|
1081
|
+
if (response.status === 403) {
|
|
1082
|
+
throw new SecureLendError(
|
|
1083
|
+
"This action is not available for this session.",
|
|
1084
|
+
"forbidden",
|
|
1085
|
+
details
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
if (response.status >= 500) {
|
|
1089
|
+
throw new ServerError("The server could not complete the request.");
|
|
1090
|
+
}
|
|
1091
|
+
throw new ValidationError(validationMessage(code), details);
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
// src/contract.ts
|
|
1096
|
+
var PLATFORM_ROUTES = {
|
|
1097
|
+
createLead: "/v1/placement/enquiries",
|
|
1098
|
+
listLeads: "/v1/leads",
|
|
1099
|
+
uploadLeadDocument: "/v1/placement/enquiries/:id/documents",
|
|
1100
|
+
compare: "/v1/placement/compare",
|
|
1101
|
+
lenderMcp: "/v1/lender/mcp",
|
|
1102
|
+
consents: "/v1/share-consents",
|
|
1103
|
+
revokeConsent: "/v1/share-consents/:id/revoke",
|
|
1104
|
+
receipts: "/v1/share-receipts",
|
|
1105
|
+
outcomes: "/v1/leads/:id/outcomes",
|
|
1106
|
+
creditBox: "/v1/credit-box",
|
|
1107
|
+
pauseCreditBox: "/v1/credit-box/pause",
|
|
1108
|
+
resumeCreditBox: "/v1/credit-box/resume",
|
|
1109
|
+
feedback: "/v1/feedback",
|
|
1110
|
+
caseFeedback: "/v1/cases/:id/feedback",
|
|
1111
|
+
liquidity: "/v1/marketplace/liquidity"
|
|
1112
|
+
};
|
|
1113
|
+
function routeWithId(template, id) {
|
|
1114
|
+
return template.replace(":id", encodeURIComponent(id));
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// src/resources/leads.ts
|
|
1118
|
+
var Leads = class {
|
|
1119
|
+
constructor(rest) {
|
|
1120
|
+
this.rest = rest;
|
|
1121
|
+
}
|
|
1122
|
+
async create(input, options = {}) {
|
|
1123
|
+
return this.rest.send({
|
|
1124
|
+
method: "POST",
|
|
1125
|
+
path: PLATFORM_ROUTES.createLead,
|
|
1126
|
+
body: input,
|
|
1127
|
+
schema: leadSchema,
|
|
1128
|
+
idempotencyKey: options.idempotencyKey
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
async list(filter = {}) {
|
|
1132
|
+
const page = await this.rest.send({
|
|
1133
|
+
method: "GET",
|
|
1134
|
+
path: PLATFORM_ROUTES.listLeads,
|
|
1135
|
+
query: { status: filter.status || "pending" },
|
|
1136
|
+
schema: leadListSchema
|
|
1137
|
+
});
|
|
1138
|
+
return page.items;
|
|
1139
|
+
}
|
|
1140
|
+
/** Lender view. A lead outside this workspace is not found. */
|
|
1141
|
+
async get(id) {
|
|
1142
|
+
const wanted = String(id || "").trim();
|
|
1143
|
+
if (!wanted) throw new ValidationError("A lead id is required.");
|
|
1144
|
+
const items = await this.list({ status: "all" });
|
|
1145
|
+
const lead = items.find((row) => row.id === wanted);
|
|
1146
|
+
if (!lead) throw new NotFoundError("The record was not found.");
|
|
1147
|
+
return leadSchema.parse(lead);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* One HTTP request per file. The platform route stores a single file.
|
|
1151
|
+
*/
|
|
1152
|
+
async uploadDocuments(enquiryId, files, options = {}) {
|
|
1153
|
+
const id = String(enquiryId || "").trim();
|
|
1154
|
+
if (!id) throw new ValidationError("A lead id is required.");
|
|
1155
|
+
if (!files?.length) throw new ValidationError("A file is required.");
|
|
1156
|
+
const saved = [];
|
|
1157
|
+
for (const file of files) {
|
|
1158
|
+
if (!file?.bytes?.byteLength) {
|
|
1159
|
+
throw new ValidationError("A file is required.");
|
|
1160
|
+
}
|
|
1161
|
+
const form = new FormData();
|
|
1162
|
+
const copy = new ArrayBuffer(file.bytes.byteLength);
|
|
1163
|
+
new Uint8Array(copy).set(file.bytes);
|
|
1164
|
+
const blob = new Blob([copy], { type: file.mime || "application/pdf" });
|
|
1165
|
+
form.append("file", blob, file.filename || "upload.pdf");
|
|
1166
|
+
if (file.documentType) form.append("documentType", file.documentType);
|
|
1167
|
+
saved.push(
|
|
1168
|
+
await this.rest.send({
|
|
1169
|
+
method: "POST",
|
|
1170
|
+
path: routeWithId(PLATFORM_ROUTES.uploadLeadDocument, id),
|
|
1171
|
+
form,
|
|
1172
|
+
schema: uploadResultSchema,
|
|
1173
|
+
idempotencyKey: options.idempotencyKey
|
|
1174
|
+
})
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
return saved;
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
var compareSchema = z.object({
|
|
1181
|
+
metadata: z.object({
|
|
1182
|
+
generationId: z.string().optional(),
|
|
1183
|
+
match: z.array(z.record(z.unknown())).optional()
|
|
1184
|
+
}).passthrough().optional()
|
|
1185
|
+
}).passthrough();
|
|
1186
|
+
var COMPARE_LOAN_PRODUCTS = /* @__PURE__ */ new Set([
|
|
1187
|
+
"personal_loans",
|
|
1188
|
+
"business_loans",
|
|
1189
|
+
"personal_mortgages",
|
|
1190
|
+
"business_mortgages",
|
|
1191
|
+
"car_loans",
|
|
1192
|
+
"student_loans"
|
|
1193
|
+
]);
|
|
1194
|
+
var COMPARE_STATIC_PRODUCTS = /* @__PURE__ */ new Set([
|
|
1195
|
+
"personal_credit_cards",
|
|
1196
|
+
"business_credit_cards",
|
|
1197
|
+
"personal_banking",
|
|
1198
|
+
"business_banking",
|
|
1199
|
+
"savings_accounts"
|
|
1200
|
+
]);
|
|
1201
|
+
function compareProductKey(value) {
|
|
1202
|
+
const raw = String(value || "").trim().toLowerCase().replace(/^compare_/, "").replace(/[\s-]+/g, "_");
|
|
1203
|
+
if (!raw) return null;
|
|
1204
|
+
if (COMPARE_LOAN_PRODUCTS.has(raw) || COMPARE_STATIC_PRODUCTS.has(raw)) {
|
|
1205
|
+
return raw;
|
|
1206
|
+
}
|
|
1207
|
+
if (raw === "personal_loan" || raw === "installment_loan") {
|
|
1208
|
+
return "personal_loans";
|
|
1209
|
+
}
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
function numberOrNull(value) {
|
|
1213
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1214
|
+
}
|
|
1215
|
+
function stringOrNull(value) {
|
|
1216
|
+
return typeof value === "string" && value ? value : null;
|
|
1217
|
+
}
|
|
1218
|
+
var Matches = class {
|
|
1219
|
+
constructor(rest) {
|
|
1220
|
+
this.rest = rest;
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Runs a match for this lead. generationId is set when outcome matching is on.
|
|
1224
|
+
* With the flag off, the lead's own lenders are returned and the ranking is unchanged.
|
|
1225
|
+
*/
|
|
1226
|
+
async list(leadId) {
|
|
1227
|
+
const id = String(leadId || "").trim();
|
|
1228
|
+
if (!id) throw new ValidationError("A lead id is required.");
|
|
1229
|
+
const page = await this.rest.send({
|
|
1230
|
+
method: "GET",
|
|
1231
|
+
path: PLATFORM_ROUTES.listLeads,
|
|
1232
|
+
query: { status: "all" },
|
|
1233
|
+
schema: leadListSchema
|
|
1234
|
+
});
|
|
1235
|
+
const lead = page.items.find((row) => row.id === id);
|
|
1236
|
+
if (!lead) throw new NotFoundError("The record was not found.");
|
|
1237
|
+
const product = compareProductKey(lead.productType);
|
|
1238
|
+
const amount = numberOrNull(lead.amount);
|
|
1239
|
+
const needsAmount = product != null && COMPARE_LOAN_PRODUCTS.has(product);
|
|
1240
|
+
if (!product || needsAmount && !(amount != null && amount > 0)) {
|
|
1241
|
+
return matchListSchema.parse({
|
|
1242
|
+
leadId: id,
|
|
1243
|
+
generationId: null,
|
|
1244
|
+
lenders: (lead.lenders || []).map((lender) => ({
|
|
1245
|
+
lenderId: lender.id ?? null,
|
|
1246
|
+
score: null,
|
|
1247
|
+
generationId: null,
|
|
1248
|
+
components: null
|
|
1249
|
+
}))
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
const compared = await this.rest.send({
|
|
1253
|
+
method: "POST",
|
|
1254
|
+
path: PLATFORM_ROUTES.compare,
|
|
1255
|
+
body: {
|
|
1256
|
+
productType: product,
|
|
1257
|
+
...amount != null && amount > 0 ? { loanAmount: amount, amount } : {}
|
|
1258
|
+
},
|
|
1259
|
+
schema: compareSchema
|
|
1260
|
+
});
|
|
1261
|
+
const generationId = compared.metadata?.generationId ?? null;
|
|
1262
|
+
const rows = compared.metadata?.match;
|
|
1263
|
+
const lenders = Array.isArray(rows) ? rows.map((row) => ({
|
|
1264
|
+
lenderId: stringOrNull(row.lenderId),
|
|
1265
|
+
score: numberOrNull(row.score),
|
|
1266
|
+
generationId,
|
|
1267
|
+
components: matchComponentsSchema.parse({
|
|
1268
|
+
rules: numberOrNull(row.rules),
|
|
1269
|
+
fit: numberOrNull(row.fit),
|
|
1270
|
+
outcome: numberOrNull(row.outcome),
|
|
1271
|
+
sba: numberOrNull(row.sba),
|
|
1272
|
+
samples: numberOrNull(row.samples)
|
|
1273
|
+
})
|
|
1274
|
+
})) : (lead.lenders || []).map((lender) => ({
|
|
1275
|
+
lenderId: lender.id ?? null,
|
|
1276
|
+
score: null,
|
|
1277
|
+
generationId: null,
|
|
1278
|
+
components: null
|
|
1279
|
+
}));
|
|
1280
|
+
return matchListSchema.parse({ leadId: id, generationId, lenders });
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
var rpcSchema = z.object({
|
|
1284
|
+
result: z.object({
|
|
1285
|
+
structuredContent: z.unknown().optional(),
|
|
1286
|
+
content: z.array(
|
|
1287
|
+
z.object({
|
|
1288
|
+
type: z.string(),
|
|
1289
|
+
text: z.string().optional()
|
|
1290
|
+
}).passthrough()
|
|
1291
|
+
).optional(),
|
|
1292
|
+
isError: z.boolean().optional()
|
|
1293
|
+
}).passthrough().optional(),
|
|
1294
|
+
error: z.object({
|
|
1295
|
+
message: z.string().optional()
|
|
1296
|
+
}).passthrough().optional()
|
|
1297
|
+
}).passthrough();
|
|
1298
|
+
function payloadOf(rpc) {
|
|
1299
|
+
const structured = rpc.result?.structuredContent;
|
|
1300
|
+
if (structured && typeof structured === "object" && !Array.isArray(structured)) {
|
|
1301
|
+
return structured;
|
|
1302
|
+
}
|
|
1303
|
+
const text = rpc.result?.content?.find((item) => item.type === "text")?.text;
|
|
1304
|
+
if (text) {
|
|
1305
|
+
try {
|
|
1306
|
+
const parsed = JSON.parse(text);
|
|
1307
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1308
|
+
return parsed;
|
|
1309
|
+
}
|
|
1310
|
+
} catch {
|
|
1311
|
+
throw new ValidationError(
|
|
1312
|
+
"The package response did not include a result."
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
throw new ValidationError("The package response did not include a result.");
|
|
1317
|
+
}
|
|
1318
|
+
function raiseToolError(payload) {
|
|
1319
|
+
const code = typeof payload.error === "string" ? payload.error : "";
|
|
1320
|
+
if (!code) return;
|
|
1321
|
+
if (code === "consent_required" || code === "consent_revoked" || code === "consent_expired") {
|
|
1322
|
+
throw new ConsentRequiredError(
|
|
1323
|
+
code === "consent_revoked" ? "This consent was revoked." : "A live consent is required before this package can be read.",
|
|
1324
|
+
{ error: code }
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
if (code === "not_found")
|
|
1328
|
+
throw new NotFoundError("The record was not found.");
|
|
1329
|
+
if (code === "sign_in_required") {
|
|
1330
|
+
throw new AuthenticationError("The API key was not accepted.");
|
|
1331
|
+
}
|
|
1332
|
+
throw new SecureLendError("The package request failed.", code, {
|
|
1333
|
+
error: code
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
var Packages = class {
|
|
1337
|
+
constructor(rest) {
|
|
1338
|
+
this.rest = rest;
|
|
1339
|
+
}
|
|
1340
|
+
async start(input) {
|
|
1341
|
+
if (!input?.borrower || input.amount == null || !input.currency || !input.security) {
|
|
1342
|
+
throw new ValidationError(
|
|
1343
|
+
"A package needs a borrower, amount, currency, and security."
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
return this.call("start_underwriting_package", { ...input });
|
|
1347
|
+
}
|
|
1348
|
+
async read(id, options = {}) {
|
|
1349
|
+
const packageId = String(id || "").trim();
|
|
1350
|
+
if (!packageId) throw new ValidationError("A package id is required.");
|
|
1351
|
+
const args = { caseId: packageId, packageId };
|
|
1352
|
+
if (options.version != null) args.packageVersion = options.version;
|
|
1353
|
+
return this.call("read_package", args);
|
|
1354
|
+
}
|
|
1355
|
+
async call(name, args) {
|
|
1356
|
+
const rpc = await this.rest.send({
|
|
1357
|
+
method: "POST",
|
|
1358
|
+
path: PLATFORM_ROUTES.lenderMcp,
|
|
1359
|
+
body: {
|
|
1360
|
+
jsonrpc: "2.0",
|
|
1361
|
+
id: 1,
|
|
1362
|
+
method: "tools/call",
|
|
1363
|
+
params: { name, arguments: args }
|
|
1364
|
+
},
|
|
1365
|
+
schema: rpcSchema
|
|
1366
|
+
});
|
|
1367
|
+
if (rpc.error) {
|
|
1368
|
+
throw new SecureLendError("The package request failed.", "mcp_error", {
|
|
1369
|
+
message: rpc.error.message
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
const payload = payloadOf(rpc);
|
|
1373
|
+
raiseToolError(payload);
|
|
1374
|
+
return packageSchema.parse(payload);
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
var consentListSchema = z.object({ items: z.array(shareConsentSchema) }).passthrough();
|
|
1378
|
+
var receiptListSchema = z.object({ items: z.array(shareReceiptSchema) }).passthrough();
|
|
1379
|
+
var Consents = class {
|
|
1380
|
+
constructor(rest) {
|
|
1381
|
+
this.rest = rest;
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* One consent row per lender id. The live route stores a row only when the
|
|
1385
|
+
* lender id is the session tenant. Omit lenderIds to use that default.
|
|
1386
|
+
*/
|
|
1387
|
+
async create(input, options = {}) {
|
|
1388
|
+
const leadId = String(input?.leadId || "").trim();
|
|
1389
|
+
const packageId = String(input?.packageId || "").trim();
|
|
1390
|
+
const borrowerRef = String(input?.borrowerRef || "").trim();
|
|
1391
|
+
if (!leadId || !packageId) {
|
|
1392
|
+
throw new ValidationError("A consent needs a lead and a package.");
|
|
1393
|
+
}
|
|
1394
|
+
if (!Number.isInteger(input.packageVersion) || input.packageVersion < 1) {
|
|
1395
|
+
throw new ValidationError("A consent needs a package version.");
|
|
1396
|
+
}
|
|
1397
|
+
if (!input.expiresAt)
|
|
1398
|
+
throw new ValidationError("A consent needs an expiry.");
|
|
1399
|
+
if (!borrowerRef)
|
|
1400
|
+
throw new ValidationError("A borrower reference is required.");
|
|
1401
|
+
if (borrowerRef.includes("@")) {
|
|
1402
|
+
throw new ValidationError(
|
|
1403
|
+
"Use a reference you choose. Do not type an email or an account number."
|
|
1404
|
+
);
|
|
1405
|
+
}
|
|
1406
|
+
const scope = input.scope?.length ? input.scope : ["summary"];
|
|
1407
|
+
if (scope.some(
|
|
1408
|
+
(item) => !SHARE_SCOPES.includes(item)
|
|
1409
|
+
)) {
|
|
1410
|
+
throw new ValidationError(
|
|
1411
|
+
"Scope must be summary, memo, checks, or documents."
|
|
1412
|
+
);
|
|
1413
|
+
}
|
|
1414
|
+
const lenderIds = input.lenderIds?.length ? input.lenderIds : [void 0];
|
|
1415
|
+
const saved = [];
|
|
1416
|
+
for (const lenderId of lenderIds) {
|
|
1417
|
+
const body = {
|
|
1418
|
+
leadId,
|
|
1419
|
+
packageId,
|
|
1420
|
+
packageVersion: input.packageVersion,
|
|
1421
|
+
scope,
|
|
1422
|
+
expiresAt: input.expiresAt,
|
|
1423
|
+
borrowerRef
|
|
1424
|
+
};
|
|
1425
|
+
if (lenderId) body.lenderId = lenderId;
|
|
1426
|
+
saved.push(
|
|
1427
|
+
await this.rest.send({
|
|
1428
|
+
method: "POST",
|
|
1429
|
+
path: PLATFORM_ROUTES.consents,
|
|
1430
|
+
body,
|
|
1431
|
+
schema: shareConsentSchema,
|
|
1432
|
+
featureGated: true,
|
|
1433
|
+
idempotencyKey: options.idempotencyKey
|
|
1434
|
+
})
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
return saved;
|
|
1438
|
+
}
|
|
1439
|
+
async list(leadId) {
|
|
1440
|
+
const page = await this.rest.send({
|
|
1441
|
+
method: "GET",
|
|
1442
|
+
path: PLATFORM_ROUTES.consents,
|
|
1443
|
+
schema: consentListSchema,
|
|
1444
|
+
featureGated: true
|
|
1445
|
+
});
|
|
1446
|
+
if (!leadId) return page.items;
|
|
1447
|
+
return page.items.filter((row) => row.leadId === leadId);
|
|
1448
|
+
}
|
|
1449
|
+
async revoke(id, options = {}) {
|
|
1450
|
+
const consentId = String(id || "").trim();
|
|
1451
|
+
if (!consentId) throw new ValidationError("A consent id is required.");
|
|
1452
|
+
return this.rest.send({
|
|
1453
|
+
method: "POST",
|
|
1454
|
+
path: routeWithId(PLATFORM_ROUTES.revokeConsent, consentId),
|
|
1455
|
+
body: {},
|
|
1456
|
+
schema: shareConsentSchema,
|
|
1457
|
+
featureGated: true,
|
|
1458
|
+
idempotencyKey: options.idempotencyKey
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
async receipts(id) {
|
|
1462
|
+
const page = await this.rest.send({
|
|
1463
|
+
method: "GET",
|
|
1464
|
+
path: PLATFORM_ROUTES.receipts,
|
|
1465
|
+
schema: receiptListSchema,
|
|
1466
|
+
featureGated: true
|
|
1467
|
+
});
|
|
1468
|
+
if (!id) return page.items;
|
|
1469
|
+
return page.items.filter((row) => row.consentId === id);
|
|
1470
|
+
}
|
|
1471
|
+
};
|
|
1472
|
+
function rawBytes(raw) {
|
|
1473
|
+
return Buffer.isBuffer(raw) ? raw : Buffer.from(raw, "utf8");
|
|
1474
|
+
}
|
|
1475
|
+
function safeEqualHex(expected, presented) {
|
|
1476
|
+
const left = Buffer.from(expected, "utf8");
|
|
1477
|
+
const right = Buffer.from(presented, "utf8");
|
|
1478
|
+
if (left.length !== right.length) return false;
|
|
1479
|
+
return timingSafeEqual(left, right);
|
|
1480
|
+
}
|
|
1481
|
+
function signOutcomePayload(raw, secret) {
|
|
1482
|
+
const digest = createHash("sha256").update(String(secret || ""), "utf8").update(".").update(rawBytes(raw)).digest("hex");
|
|
1483
|
+
return `sha256=${digest}`;
|
|
1484
|
+
}
|
|
1485
|
+
function signWebhook(raw, secret, timestampSec = Math.floor(Date.now() / 1e3)) {
|
|
1486
|
+
const mac = createHmac("sha256", String(secret || "")).update(`${timestampSec}.`).update(rawBytes(raw)).digest("hex");
|
|
1487
|
+
return `t=${timestampSec},v1=${mac}`;
|
|
1488
|
+
}
|
|
1489
|
+
function readStamp(header) {
|
|
1490
|
+
const match = header.match(/(?:^|,\s*)t=(\d+),v1=([a-fA-F0-9]+)/);
|
|
1491
|
+
if (!match) return null;
|
|
1492
|
+
return { t: Number(match[1]), mac: match[2].toLowerCase() };
|
|
1493
|
+
}
|
|
1494
|
+
function verifyWebhook(rawBody, signatureHeader, secret, options = {}) {
|
|
1495
|
+
const header = String(signatureHeader || "").trim();
|
|
1496
|
+
const key = String(secret || "");
|
|
1497
|
+
if (!key || !header) {
|
|
1498
|
+
throw new WebhookSignatureError("Webhook signature did not match.");
|
|
1499
|
+
}
|
|
1500
|
+
const stamp = readStamp(header);
|
|
1501
|
+
if (stamp) {
|
|
1502
|
+
const tolerance = options.toleranceSec ?? 300;
|
|
1503
|
+
const nowSec = Math.floor((options.nowMs ?? Date.now()) / 1e3);
|
|
1504
|
+
if (!Number.isFinite(stamp.t) || Math.abs(nowSec - stamp.t) > tolerance) {
|
|
1505
|
+
throw new WebhookSignatureError(
|
|
1506
|
+
"This webhook is outside the time window."
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1509
|
+
const expected2 = signWebhook(rawBody, key, stamp.t).split("v1=")[1] || "";
|
|
1510
|
+
if (!safeEqualHex(expected2, stamp.mac)) {
|
|
1511
|
+
throw new WebhookSignatureError("Webhook signature did not match.");
|
|
1512
|
+
}
|
|
1513
|
+
return true;
|
|
1514
|
+
}
|
|
1515
|
+
const presented = header.replace(/^sha256=/i, "").trim().toLowerCase();
|
|
1516
|
+
const expected = signOutcomePayload(rawBody, key).replace(/^sha256=/, "");
|
|
1517
|
+
if (!safeEqualHex(expected, presented)) {
|
|
1518
|
+
throw new WebhookSignatureError("Webhook signature did not match.");
|
|
1519
|
+
}
|
|
1520
|
+
return true;
|
|
1521
|
+
}
|
|
1522
|
+
function parseEvent(input) {
|
|
1523
|
+
try {
|
|
1524
|
+
const event = secureLendEventSchema.parse(input);
|
|
1525
|
+
return { ...event, data: event.data ?? {} };
|
|
1526
|
+
} catch (err) {
|
|
1527
|
+
if (err instanceof z.ZodError) {
|
|
1528
|
+
throw new ValidationError(
|
|
1529
|
+
"The webhook event did not match the expected shape.",
|
|
1530
|
+
{
|
|
1531
|
+
issues: err.issues.map((issue) => issue.path.join("."))
|
|
1532
|
+
}
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
throw err;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// src/resources/outcomes.ts
|
|
1540
|
+
var outcomeListSchema = z.object({ items: z.array(leadOutcomeSchema) }).passthrough();
|
|
1541
|
+
var Outcomes = class {
|
|
1542
|
+
constructor(rest) {
|
|
1543
|
+
this.rest = rest;
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Declined without a reason code, and funded without an amount and date,
|
|
1547
|
+
* fail here and do not call the server.
|
|
1548
|
+
*/
|
|
1549
|
+
async record(leadId, input, options = {}) {
|
|
1550
|
+
const id = String(leadId || "").trim();
|
|
1551
|
+
const state = String(input?.state || "").trim();
|
|
1552
|
+
if (!id) throw new ValidationError("A lead id is required.");
|
|
1553
|
+
if (!OUTCOME_STATES.includes(state)) {
|
|
1554
|
+
throw new ValidationError("That outcome state is not recognized.");
|
|
1555
|
+
}
|
|
1556
|
+
const reasonCode = input.reasonCode ? String(input.reasonCode).trim() : "";
|
|
1557
|
+
if (state === "declined") {
|
|
1558
|
+
if (!DECLINE_REASON_CODES.includes(
|
|
1559
|
+
reasonCode
|
|
1560
|
+
)) {
|
|
1561
|
+
throw new ValidationError("A decline needs a reason code.");
|
|
1562
|
+
}
|
|
1563
|
+
if (reasonCode === "other" && !String(input.reasonText || "").trim()) {
|
|
1564
|
+
throw new ValidationError("A decline needs a reason code.");
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
if (state === "funded") {
|
|
1568
|
+
const amount = Number(input.fundedAmount);
|
|
1569
|
+
const date = String(input.fundedOn || "").trim();
|
|
1570
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
1571
|
+
throw new ValidationError("Funded needs an amount.");
|
|
1572
|
+
}
|
|
1573
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
1574
|
+
throw new ValidationError("Funded needs a date as YYYY-MM-DD.");
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
const body = { state };
|
|
1578
|
+
if (input.lenderId) body.lenderId = input.lenderId;
|
|
1579
|
+
if (reasonCode) body.reasonCode = reasonCode;
|
|
1580
|
+
if (input.reasonText) body.reasonText = input.reasonText;
|
|
1581
|
+
if (state === "funded") {
|
|
1582
|
+
body.fundedAmount = Number(input.fundedAmount);
|
|
1583
|
+
body.fundedOn = String(input.fundedOn);
|
|
1584
|
+
}
|
|
1585
|
+
if (input.surface) body.surface = input.surface;
|
|
1586
|
+
return this.rest.send({
|
|
1587
|
+
method: "POST",
|
|
1588
|
+
path: routeWithId(PLATFORM_ROUTES.outcomes, id),
|
|
1589
|
+
body,
|
|
1590
|
+
schema: leadOutcomeSchema,
|
|
1591
|
+
featureGated: true,
|
|
1592
|
+
idempotencyKey: options.idempotencyKey
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
async list(leadId) {
|
|
1596
|
+
const id = String(leadId || "").trim();
|
|
1597
|
+
if (!id) throw new ValidationError("A lead id is required.");
|
|
1598
|
+
const page = await this.rest.send({
|
|
1599
|
+
method: "GET",
|
|
1600
|
+
path: routeWithId(PLATFORM_ROUTES.outcomes, id),
|
|
1601
|
+
schema: outcomeListSchema,
|
|
1602
|
+
featureGated: true
|
|
1603
|
+
});
|
|
1604
|
+
return page.items;
|
|
1605
|
+
}
|
|
1606
|
+
/** Header value for POST /v1/webhooks/lender-outcomes. */
|
|
1607
|
+
signPayload(rawBody, secret) {
|
|
1608
|
+
return signOutcomePayload(rawBody, secret);
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
|
|
1612
|
+
// src/resources/creditBox.ts
|
|
1613
|
+
var CreditBoxClient = class {
|
|
1614
|
+
constructor(rest) {
|
|
1615
|
+
this.rest = rest;
|
|
1616
|
+
}
|
|
1617
|
+
async get() {
|
|
1618
|
+
return this.rest.send({
|
|
1619
|
+
method: "GET",
|
|
1620
|
+
path: PLATFORM_ROUTES.creditBox,
|
|
1621
|
+
schema: creditBoxViewSchema,
|
|
1622
|
+
featureGated: true
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
async put(input, options = {}) {
|
|
1626
|
+
const body = writeBody(input);
|
|
1627
|
+
return this.rest.send({
|
|
1628
|
+
method: "PUT",
|
|
1629
|
+
path: PLATFORM_ROUTES.creditBox,
|
|
1630
|
+
body,
|
|
1631
|
+
schema: creditBoxSchema,
|
|
1632
|
+
featureGated: true,
|
|
1633
|
+
idempotencyKey: options.idempotencyKey
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
async pause(input = {}, options = {}) {
|
|
1637
|
+
return this.rest.send({
|
|
1638
|
+
method: "POST",
|
|
1639
|
+
path: PLATFORM_ROUTES.pauseCreditBox,
|
|
1640
|
+
body: input.until ? { until: input.until } : {},
|
|
1641
|
+
schema: creditBoxSchema,
|
|
1642
|
+
featureGated: true,
|
|
1643
|
+
idempotencyKey: options.idempotencyKey
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
async resume(options = {}) {
|
|
1647
|
+
return this.rest.send({
|
|
1648
|
+
method: "POST",
|
|
1649
|
+
path: PLATFORM_ROUTES.resumeCreditBox,
|
|
1650
|
+
body: {},
|
|
1651
|
+
schema: creditBoxSchema,
|
|
1652
|
+
featureGated: true,
|
|
1653
|
+
idempotencyKey: options.idempotencyKey
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1657
|
+
function writeBody(input) {
|
|
1658
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
1659
|
+
throw new ValidationError("A credit box needs criteria.");
|
|
1660
|
+
}
|
|
1661
|
+
if ("criteria" in input) {
|
|
1662
|
+
const criteria = input.criteria;
|
|
1663
|
+
if (!criteria || typeof criteria !== "object" || Array.isArray(criteria)) {
|
|
1664
|
+
throw new ValidationError("A credit box needs criteria.");
|
|
1665
|
+
}
|
|
1666
|
+
return input;
|
|
1667
|
+
}
|
|
1668
|
+
return { criteria: input };
|
|
1669
|
+
}
|
|
1670
|
+
var feedbackListSchema = z.object({ items: z.array(feedbackEventSchema) }).passthrough();
|
|
1671
|
+
var Feedback = class {
|
|
1672
|
+
constructor(rest) {
|
|
1673
|
+
this.rest = rest;
|
|
1674
|
+
}
|
|
1675
|
+
async submit(event, options = {}) {
|
|
1676
|
+
const type = String(event?.type || "").trim();
|
|
1677
|
+
const kind = String(event?.target?.kind || "").trim();
|
|
1678
|
+
const key = String(event?.target?.key || "").trim();
|
|
1679
|
+
if (!FEEDBACK_TYPES.includes(type)) {
|
|
1680
|
+
throw new ValidationError("That feedback type is not recognized.");
|
|
1681
|
+
}
|
|
1682
|
+
if (!FEEDBACK_TARGETS.includes(kind) || !key) {
|
|
1683
|
+
throw new ValidationError("Feedback needs a target.");
|
|
1684
|
+
}
|
|
1685
|
+
if (type === "rating") {
|
|
1686
|
+
const rating = Number(event.rating);
|
|
1687
|
+
if (!Number.isInteger(rating) || rating < 1 || rating > 9) {
|
|
1688
|
+
throw new ValidationError("A rating is a whole number from 1 to 9.");
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return this.rest.send({
|
|
1692
|
+
method: "POST",
|
|
1693
|
+
path: PLATFORM_ROUTES.feedback,
|
|
1694
|
+
body: {
|
|
1695
|
+
type,
|
|
1696
|
+
surface: event.surface || "api",
|
|
1697
|
+
target: { kind, key },
|
|
1698
|
+
...event.rating != null ? { rating: event.rating } : {},
|
|
1699
|
+
...event.comment ? { comment: event.comment } : {},
|
|
1700
|
+
...event.generationId ? { generationId: event.generationId } : {},
|
|
1701
|
+
...event.caseId ? { caseId: event.caseId } : {},
|
|
1702
|
+
...event.before != null ? { before: event.before } : {},
|
|
1703
|
+
...event.after != null ? { after: event.after } : {}
|
|
1704
|
+
},
|
|
1705
|
+
schema: feedbackCreatedSchema,
|
|
1706
|
+
featureGated: true,
|
|
1707
|
+
idempotencyKey: options.idempotencyKey
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
async listForCase(caseId) {
|
|
1711
|
+
const id = String(caseId || "").trim();
|
|
1712
|
+
if (!id) throw new ValidationError("A case id is required.");
|
|
1713
|
+
const page = await this.rest.send({
|
|
1714
|
+
method: "GET",
|
|
1715
|
+
path: routeWithId(PLATFORM_ROUTES.caseFeedback, id),
|
|
1716
|
+
schema: feedbackListSchema,
|
|
1717
|
+
featureGated: true
|
|
1718
|
+
});
|
|
1719
|
+
return page.items;
|
|
1720
|
+
}
|
|
1721
|
+
};
|
|
1722
|
+
|
|
1723
|
+
// src/resources/liquidity.ts
|
|
1724
|
+
var Liquidity = class {
|
|
1725
|
+
constructor(rest) {
|
|
1726
|
+
this.rest = rest;
|
|
1727
|
+
}
|
|
1728
|
+
async get(query = {}) {
|
|
1729
|
+
return this.rest.send({
|
|
1730
|
+
method: "GET",
|
|
1731
|
+
path: PLATFORM_ROUTES.liquidity,
|
|
1732
|
+
query: {
|
|
1733
|
+
groupBy: query.groupBy || "product",
|
|
1734
|
+
from: query.from,
|
|
1735
|
+
to: query.to
|
|
1736
|
+
},
|
|
1737
|
+
schema: liquidityReportSchema,
|
|
1738
|
+
featureGated: true
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
698
1742
|
|
|
699
1743
|
// src/client.ts
|
|
700
1744
|
var SecureLend = class {
|
|
@@ -710,8 +1754,28 @@ var SecureLend = class {
|
|
|
710
1754
|
}
|
|
711
1755
|
this.mcpClient = new MCPClient({ apiKey: config?.apiKey || "", mcpURL });
|
|
712
1756
|
this.mcp = this.mcpClient.mcp;
|
|
1757
|
+
let apiBaseUrl = config?.apiBaseUrl || "https://securelend.ai";
|
|
1758
|
+
if (typeof window !== "undefined" && apiBaseUrl.startsWith("/")) {
|
|
1759
|
+
apiBaseUrl = window.location.origin + apiBaseUrl;
|
|
1760
|
+
}
|
|
1761
|
+
this.rest = new RestClient({
|
|
1762
|
+
baseUrl: apiBaseUrl,
|
|
1763
|
+
apiKey: config?.apiKey,
|
|
1764
|
+
fetchImpl: config?.fetch
|
|
1765
|
+
});
|
|
1766
|
+
this.leads = new Leads(this.rest);
|
|
1767
|
+
this.matches = new Matches(this.rest);
|
|
1768
|
+
this.packages = new Packages(this.rest);
|
|
1769
|
+
this.consents = new Consents(this.rest);
|
|
1770
|
+
this.outcomes = new Outcomes(this.rest);
|
|
1771
|
+
this.creditBox = new CreditBoxClient(this.rest);
|
|
1772
|
+
this.feedback = new Feedback(this.rest);
|
|
1773
|
+
this.liquidity = new Liquidity(this.rest);
|
|
1774
|
+
this.webhooks = { verify: verifyWebhook, parseEvent, sign: signWebhook };
|
|
713
1775
|
}
|
|
714
1776
|
// --- Loan Comparison ---
|
|
1777
|
+
// These methods call the comparison MCP (default https://mcp.securelend.ai/mcp).
|
|
1778
|
+
// They are not sent to /v1/lender/mcp.
|
|
715
1779
|
async comparePersonalLoans(request) {
|
|
716
1780
|
return this.callTool(
|
|
717
1781
|
"compare_personal_loans",
|
|
@@ -877,6 +1941,7 @@ var SecureLend = class {
|
|
|
877
1941
|
*/
|
|
878
1942
|
setApiKey(apiKey) {
|
|
879
1943
|
this.mcpClient.setApiKey(apiKey);
|
|
1944
|
+
this.rest.setApiKey(apiKey);
|
|
880
1945
|
}
|
|
881
1946
|
// --- Private Helpers ---
|
|
882
1947
|
async callTool(toolName, request, schema) {
|
|
@@ -937,6 +2002,6 @@ var SecureLend = class {
|
|
|
937
2002
|
}
|
|
938
2003
|
};
|
|
939
2004
|
|
|
940
|
-
export { ApplicationStatus, ApplicationType, AuthenticationError, CreditTier, NetworkError, NotFoundError, ProductType, ProviderStatus, RateLimitError, SecureLend, SecureLendError, ServerError, ValidationError, anyOfferSchema, applicationProviderSchema, autoLoanSearchSchema, bankAccountOfferSchema, businessApplicantSchema, businessBankingComparisonResponseSchema, businessBankingOfferSchema, businessBankingSearchSchema, businessCreditCardComparisonResponseSchema, businessCreditCardOfferSchema, businessCreditCardSearchSchema, businessLoanSearchSchema, createBusinessApplicationSchema, createPersonalApplicationSchema, creditCardOfferSchema, displayOfferFormResponseSchema, displayOfferFormSchema, displayUploadDocumentsFormResponseSchema, displayUploadDocumentsFormSchema, documentTypeEnum, getMultipleOffersSchema, getOfferSchema, leaseVsPurchaseResponseSchema, leaseVsPurchaseSchema, loanComparisonResponseSchema, loanOfferSchema, loanPaymentResponseSchema, loanPaymentSchema, mortgagePaymentResponseSchema, mortgagePaymentSchema, mortgageSearchSchema, personalApplicantSchema, personalApplicationSchema, personalBankingComparisonResponseSchema, personalBankingOfferSchema, personalBankingSearchSchema, personalCreditCardComparisonResponseSchema, personalCreditCardOfferSchema, personalCreditCardSearchSchema, personalLoanSearchSchema, savingsComparisonResponseSchema, savingsOfferSchema, savingsSearchSchema, studentLoanSearchSchema, submitDocumentsResponseSchema, submitDocumentsSchema, trackOfferStatusResponseSchema, trackOfferStatusSchema };
|
|
2005
|
+
export { ApplicationStatus, ApplicationType, AuthenticationError, ConsentRequiredError, CreditTier, DECLINE_REASON_CODES, EVENT_TYPES, FEEDBACK_TARGETS, FEEDBACK_TYPES, FeatureDisabledError, InvalidTransitionError, NetworkError, NotFoundError, OUTCOME_STATES, ProductType, ProviderStatus, RateLimitError, SHARE_SCOPES, SecureLend, SecureLendError, ServerError, ValidationError, WebhookSignatureError, anyOfferSchema, applicationProviderSchema, autoLoanSearchSchema, bankAccountOfferSchema, businessApplicantSchema, businessBankingComparisonResponseSchema, businessBankingOfferSchema, businessBankingSearchSchema, businessCreditCardComparisonResponseSchema, businessCreditCardOfferSchema, businessCreditCardSearchSchema, businessLoanSearchSchema, createBusinessApplicationSchema, createPersonalApplicationSchema, creditBoxSchema, creditBoxViewSchema, creditCardOfferSchema, displayOfferFormResponseSchema, displayOfferFormSchema, displayUploadDocumentsFormResponseSchema, displayUploadDocumentsFormSchema, documentTypeEnum, feedbackCreatedSchema, feedbackEventSchema, getMultipleOffersSchema, getOfferSchema, leadListSchema, leadOutcomeSchema, leadSchema, leaseVsPurchaseResponseSchema, leaseVsPurchaseSchema, lenderMatchSchema, liquidityCellSchema, liquidityReportSchema, loanComparisonResponseSchema, loanOfferSchema, loanPaymentResponseSchema, loanPaymentSchema, matchComponentsSchema, matchListSchema, mortgagePaymentResponseSchema, mortgagePaymentSchema, mortgageSearchSchema, packageSchema, parseEvent, personalApplicantSchema, personalApplicationSchema, personalBankingComparisonResponseSchema, personalBankingOfferSchema, personalBankingSearchSchema, personalCreditCardComparisonResponseSchema, personalCreditCardOfferSchema, personalCreditCardSearchSchema, personalLoanSearchSchema, savingsComparisonResponseSchema, savingsOfferSchema, savingsSearchSchema, secureLendEventSchema, shareConsentSchema, shareReceiptSchema, signOutcomePayload, signWebhook, studentLoanSearchSchema, submitDocumentsResponseSchema, submitDocumentsSchema, trackOfferStatusResponseSchema, trackOfferStatusSchema, uploadResultSchema, verifyWebhook };
|
|
941
2006
|
//# sourceMappingURL=index.mjs.map
|
|
942
2007
|
//# sourceMappingURL=index.mjs.map
|