securelend 0.1.0__py3-none-any.whl
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.
- securelend/__init__.py +31 -0
- securelend/client.py +348 -0
- securelend/types.py +475 -0
- securelend/utils/__init__.py +1 -0
- securelend/utils/errors.py +38 -0
- securelend/utils/mcp.py +115 -0
- securelend-0.1.0.dist-info/METADATA +70 -0
- securelend-0.1.0.dist-info/RECORD +10 -0
- securelend-0.1.0.dist-info/WHEEL +5 -0
- securelend-0.1.0.dist-info/top_level.txt +1 -0
securelend/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SecureLend SDK for Python
|
|
3
|
+
|
|
4
|
+
Official SDK for SecureLend - Financial services infrastructure for AI
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
|
|
9
|
+
from .client import SecureLend
|
|
10
|
+
from .utils.errors import (
|
|
11
|
+
AuthenticationError,
|
|
12
|
+
NetworkError,
|
|
13
|
+
NotFoundError,
|
|
14
|
+
RateLimitError,
|
|
15
|
+
SecureLendError,
|
|
16
|
+
ServerError,
|
|
17
|
+
ValidationError,
|
|
18
|
+
)
|
|
19
|
+
from . import types
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"SecureLend",
|
|
23
|
+
"SecureLendError",
|
|
24
|
+
"AuthenticationError",
|
|
25
|
+
"RateLimitError",
|
|
26
|
+
"ValidationError",
|
|
27
|
+
"NotFoundError",
|
|
28
|
+
"NetworkError",
|
|
29
|
+
"ServerError",
|
|
30
|
+
"types",
|
|
31
|
+
]
|
securelend/client.py
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, Dict, Optional, Type, TypeVar
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ValidationError as PydanticValidationError
|
|
5
|
+
|
|
6
|
+
from . import types
|
|
7
|
+
from .utils.errors import SecureLendError, ValidationError
|
|
8
|
+
from .utils.mcp import MCPClient
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T", bound=BaseModel)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SecureLend:
|
|
14
|
+
"""
|
|
15
|
+
SecureLend MCP-native SDK Client
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
>>> import asyncio
|
|
19
|
+
>>> from securelend import SecureLend
|
|
20
|
+
>>>
|
|
21
|
+
>>> async def main():
|
|
22
|
+
... securelend = SecureLend(api_key="sk_test_...")
|
|
23
|
+
... loans = await securelend.compare_business_loans({
|
|
24
|
+
... "loanAmount": 200000,
|
|
25
|
+
... "purpose": "equipment",
|
|
26
|
+
... "annualRevenue": 1200000,
|
|
27
|
+
... })
|
|
28
|
+
... print(loans)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self, *, api_key: Optional[str] = None, server_url: Optional[str] = None
|
|
33
|
+
):
|
|
34
|
+
mcp_url = server_url or "https://mcp.securelend.ai/mcp"
|
|
35
|
+
self._mcp_client = MCPClient(api_key=api_key or "", mcp_url=mcp_url)
|
|
36
|
+
|
|
37
|
+
# --- Loan Comparison ---
|
|
38
|
+
|
|
39
|
+
async def compare_personal_loans(
|
|
40
|
+
self, request: Dict[str, Any]
|
|
41
|
+
) -> types.LoanComparisonResponse:
|
|
42
|
+
return await self._validated_call_tool(
|
|
43
|
+
"compare_personal_loans",
|
|
44
|
+
request,
|
|
45
|
+
types.PersonalLoanSearchParams,
|
|
46
|
+
types.LoanComparisonResponse,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
async def compare_business_loans(
|
|
50
|
+
self, request: Dict[str, Any]
|
|
51
|
+
) -> types.LoanComparisonResponse:
|
|
52
|
+
return await self._validated_call_tool(
|
|
53
|
+
"compare_business_loans",
|
|
54
|
+
request,
|
|
55
|
+
types.BusinessLoanSearchParams,
|
|
56
|
+
types.LoanComparisonResponse,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
async def compare_personal_mortgages(
|
|
60
|
+
self, request: Dict[str, Any]
|
|
61
|
+
) -> types.LoanComparisonResponse:
|
|
62
|
+
return await self._validated_call_tool(
|
|
63
|
+
"compare_personal_mortgages",
|
|
64
|
+
request,
|
|
65
|
+
types.MortgageSearchParams,
|
|
66
|
+
types.LoanComparisonResponse,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
async def compare_business_mortgages(
|
|
70
|
+
self, request: Dict[str, Any]
|
|
71
|
+
) -> types.LoanComparisonResponse:
|
|
72
|
+
return await self._validated_call_tool(
|
|
73
|
+
"compare_business_mortgages",
|
|
74
|
+
request,
|
|
75
|
+
types.MortgageSearchParams,
|
|
76
|
+
types.LoanComparisonResponse,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
async def compare_car_loans(
|
|
80
|
+
self, request: Dict[str, Any]
|
|
81
|
+
) -> types.LoanComparisonResponse:
|
|
82
|
+
return await self._validated_call_tool(
|
|
83
|
+
"compare_car_loans",
|
|
84
|
+
request,
|
|
85
|
+
types.AutoLoanSearchParams,
|
|
86
|
+
types.LoanComparisonResponse,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
async def compare_student_loans(
|
|
90
|
+
self, request: Dict[str, Any]
|
|
91
|
+
) -> types.LoanComparisonResponse:
|
|
92
|
+
return await self._validated_call_tool(
|
|
93
|
+
"compare_student_loans",
|
|
94
|
+
request,
|
|
95
|
+
types.StudentLoanSearchParams,
|
|
96
|
+
types.LoanComparisonResponse,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# --- Banking & Credit Cards ---
|
|
100
|
+
|
|
101
|
+
async def compare_business_banking(
|
|
102
|
+
self, request: Dict[str, Any]
|
|
103
|
+
) -> types.BusinessBankingComparisonResponse:
|
|
104
|
+
return await self._validated_call_tool(
|
|
105
|
+
"compare_business_banking",
|
|
106
|
+
request,
|
|
107
|
+
types.BusinessBankingSearchSchema,
|
|
108
|
+
types.BusinessBankingComparisonResponse,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
async def compare_personal_banking(
|
|
112
|
+
self, request: Dict[str, Any]
|
|
113
|
+
) -> types.PersonalBankingComparisonResponse:
|
|
114
|
+
return await self._validated_call_tool(
|
|
115
|
+
"compare_personal_banking",
|
|
116
|
+
request,
|
|
117
|
+
types.PersonalBankingSearchSchema,
|
|
118
|
+
types.PersonalBankingComparisonResponse,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
async def compare_savings_accounts(
|
|
122
|
+
self, request: Dict[str, Any]
|
|
123
|
+
) -> types.SavingsAccountComparisonResponse:
|
|
124
|
+
return await self._validated_call_tool(
|
|
125
|
+
"compare_savings_accounts",
|
|
126
|
+
request,
|
|
127
|
+
types.SavingsSearchSchema,
|
|
128
|
+
types.SavingsAccountComparisonResponse,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
async def compare_business_credit_cards(
|
|
132
|
+
self, request: Dict[str, Any]
|
|
133
|
+
) -> types.BusinessCreditCardComparisonResponse:
|
|
134
|
+
return await self._validated_call_tool(
|
|
135
|
+
"compare_business_credit_cards",
|
|
136
|
+
request,
|
|
137
|
+
types.BusinessCreditCardSearchParams,
|
|
138
|
+
types.BusinessCreditCardComparisonResponse,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
async def compare_personal_credit_cards(
|
|
142
|
+
self, request: Dict[str, Any]
|
|
143
|
+
) -> types.PersonalCreditCardComparisonResponse:
|
|
144
|
+
return await self._validated_call_tool(
|
|
145
|
+
"compare_personal_credit_cards",
|
|
146
|
+
request,
|
|
147
|
+
types.PersonalCreditCardSearchSchema,
|
|
148
|
+
types.PersonalCreditCardComparisonResponse,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# --- Financial Calculators ---
|
|
152
|
+
|
|
153
|
+
async def calculate_loan_payment(
|
|
154
|
+
self, request: Dict[str, Any]
|
|
155
|
+
) -> types.LoanCalculationResponse:
|
|
156
|
+
return await self._validated_call_tool(
|
|
157
|
+
"calculate_loan_payment",
|
|
158
|
+
request,
|
|
159
|
+
types.LoanPaymentParams,
|
|
160
|
+
types.LoanCalculationResponse,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
async def calculate_mortgage_payment(
|
|
164
|
+
self, request: Dict[str, Any]
|
|
165
|
+
) -> types.MortgageCalculationResponse:
|
|
166
|
+
return await self._validated_call_tool(
|
|
167
|
+
"calculate_mortgage_payment",
|
|
168
|
+
request,
|
|
169
|
+
types.MortgagePaymentParams,
|
|
170
|
+
types.MortgageCalculationResponse,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
async def compare_lease_vs_purchase(
|
|
174
|
+
self, request: Dict[str, Any]
|
|
175
|
+
) -> types.LeaseVsPurchaseResponse:
|
|
176
|
+
return await self._validated_call_tool(
|
|
177
|
+
"compare_lease_vs_purchase",
|
|
178
|
+
request,
|
|
179
|
+
types.LeaseVsPurchaseParams,
|
|
180
|
+
types.LeaseVsPurchaseResponse,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# --- Application Management ---
|
|
184
|
+
|
|
185
|
+
async def get_offer(self, request: Dict[str, Any]) -> types.PersonalApplication:
|
|
186
|
+
return await self._validated_call_tool(
|
|
187
|
+
"get_offer", request, types.GetOfferParams, types.PersonalApplication
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
async def get_multiple_offers(
|
|
191
|
+
self, request: Dict[str, Any]
|
|
192
|
+
) -> types.PersonalApplication:
|
|
193
|
+
return await self._validated_call_tool(
|
|
194
|
+
"get_multiple_offers",
|
|
195
|
+
request,
|
|
196
|
+
types.GetMultipleOffersParams,
|
|
197
|
+
types.PersonalApplication,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
async def display_offer_form(
|
|
201
|
+
self, request: Dict[str, Any]
|
|
202
|
+
) -> types.DisplayOfferFormResponse:
|
|
203
|
+
return await self._validated_call_tool(
|
|
204
|
+
"display_offer_form",
|
|
205
|
+
request,
|
|
206
|
+
types.DisplayOfferFormParams,
|
|
207
|
+
types.DisplayOfferFormResponse,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
async def track_offer_status(
|
|
211
|
+
self, request: Dict[str, Any]
|
|
212
|
+
) -> types.TrackOfferStatusResponse:
|
|
213
|
+
return await self._validated_call_tool(
|
|
214
|
+
"track_offer_status",
|
|
215
|
+
request,
|
|
216
|
+
types.TrackOfferStatusParams,
|
|
217
|
+
types.TrackOfferStatusResponse,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
async def display_upload_documents_form(
|
|
221
|
+
self, request: Dict[str, Any]
|
|
222
|
+
) -> types.DisplayUploadDocumentsFormResponse:
|
|
223
|
+
return await self._validated_call_tool(
|
|
224
|
+
"display_upload_documents_form",
|
|
225
|
+
request,
|
|
226
|
+
types.DisplayUploadDocumentsFormParams,
|
|
227
|
+
types.DisplayUploadDocumentsFormResponse,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
async def submit_documents(
|
|
231
|
+
self, request: Dict[str, Any]
|
|
232
|
+
) -> types.SubmitDocumentsResponse:
|
|
233
|
+
return await self._validated_call_tool(
|
|
234
|
+
"submit_documents",
|
|
235
|
+
request,
|
|
236
|
+
types.SubmitDocumentsParams,
|
|
237
|
+
types.SubmitDocumentsResponse,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# --- Core Methods ---
|
|
241
|
+
|
|
242
|
+
async def connect(self) -> None:
|
|
243
|
+
"""Manually connect to the MCP server."""
|
|
244
|
+
await self._mcp_client.connect()
|
|
245
|
+
|
|
246
|
+
def set_api_key(self, api_key: str) -> None:
|
|
247
|
+
"""Update API key for multi-tenant applications."""
|
|
248
|
+
self._mcp_client.set_api_key(api_key)
|
|
249
|
+
|
|
250
|
+
def enable_debug(self) -> None:
|
|
251
|
+
"""Enable debug logging."""
|
|
252
|
+
self._mcp_client.enable_debug()
|
|
253
|
+
|
|
254
|
+
def disable_debug(self) -> None:
|
|
255
|
+
"""Disable debug logging."""
|
|
256
|
+
self._mcp_client.disable_debug()
|
|
257
|
+
|
|
258
|
+
async def aclose(self):
|
|
259
|
+
"""Gracefully close the connection to the MCP server."""
|
|
260
|
+
await self._mcp_client.close()
|
|
261
|
+
|
|
262
|
+
async def __aenter__(self):
|
|
263
|
+
return self
|
|
264
|
+
|
|
265
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
266
|
+
await self.aclose()
|
|
267
|
+
|
|
268
|
+
# --- Private Helpers ---
|
|
269
|
+
|
|
270
|
+
async def _validated_call_tool(
|
|
271
|
+
self,
|
|
272
|
+
tool_name: str,
|
|
273
|
+
request_data: Dict[str, Any],
|
|
274
|
+
request_cls: Type[BaseModel],
|
|
275
|
+
response_cls: Type[T],
|
|
276
|
+
) -> T:
|
|
277
|
+
try:
|
|
278
|
+
validated_request = request_cls.model_validate(request_data)
|
|
279
|
+
except PydanticValidationError as e:
|
|
280
|
+
raise ValidationError("Invalid request parameters", errors=e.errors()) from e
|
|
281
|
+
return await self._call_tool(tool_name, validated_request, response_cls)
|
|
282
|
+
|
|
283
|
+
async def _call_tool(
|
|
284
|
+
self, tool_name: str, request: BaseModel, response_cls: Type[T]
|
|
285
|
+
) -> T:
|
|
286
|
+
# Pydantic models are passed in, dump to dict for MCP call
|
|
287
|
+
request_data = request.model_dump(by_alias=True, exclude_none=True)
|
|
288
|
+
tool_result_dict = await self._mcp_client.call_tool(tool_name, request_data)
|
|
289
|
+
tool_result = types.ToolResult.model_validate(tool_result_dict)
|
|
290
|
+
data = self._parse_json_response(tool_result)
|
|
291
|
+
data["widget"] = self._get_widget(tool_result)
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
return response_cls.model_validate(data)
|
|
295
|
+
except PydanticValidationError as e:
|
|
296
|
+
raise ValidationError(
|
|
297
|
+
f"Invalid response from MCP server: failed to validate JSON content for tool '{tool_name}'",
|
|
298
|
+
errors=e.errors(),
|
|
299
|
+
) from e
|
|
300
|
+
|
|
301
|
+
def _parse_json_response(self, tool_result: types.ToolResult) -> Dict[str, Any]:
|
|
302
|
+
if tool_result.structured_content:
|
|
303
|
+
return tool_result.structured_content
|
|
304
|
+
|
|
305
|
+
content_list = tool_result.content
|
|
306
|
+
if not isinstance(content_list, list):
|
|
307
|
+
raise SecureLendError(
|
|
308
|
+
"Invalid response: content is not a list", "server_error"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
for item in content_list:
|
|
312
|
+
if item.type == "text" and item.text:
|
|
313
|
+
try:
|
|
314
|
+
return json.loads(item.text)
|
|
315
|
+
except json.JSONDecodeError as e:
|
|
316
|
+
raise SecureLendError(
|
|
317
|
+
f"Failed to parse JSON response: {e}", "server_error"
|
|
318
|
+
) from e
|
|
319
|
+
elif (
|
|
320
|
+
item.type == "resource"
|
|
321
|
+
and item.resource
|
|
322
|
+
and item.resource.mime_type == "application/json"
|
|
323
|
+
and item.resource.text
|
|
324
|
+
):
|
|
325
|
+
try:
|
|
326
|
+
return json.loads(item.resource.text)
|
|
327
|
+
except json.JSONDecodeError as e:
|
|
328
|
+
raise SecureLendError(
|
|
329
|
+
f"Failed to parse JSON response: {e}", "server_error"
|
|
330
|
+
) from e
|
|
331
|
+
|
|
332
|
+
raise SecureLendError(
|
|
333
|
+
"Invalid response from MCP server: missing JSON content", "server_error"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def _get_widget(self, tool_result: types.ToolResult) -> Optional[str]:
|
|
337
|
+
content_list = tool_result.content
|
|
338
|
+
if not isinstance(content_list, list):
|
|
339
|
+
return None
|
|
340
|
+
|
|
341
|
+
for item in content_list:
|
|
342
|
+
if (
|
|
343
|
+
item.type == "resource"
|
|
344
|
+
and item.resource
|
|
345
|
+
and item.resource.mime_type == "text/html"
|
|
346
|
+
):
|
|
347
|
+
return item.resource.text
|
|
348
|
+
return None
|
securelend/types.py
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import List, Literal, Optional, Union, Dict, Any
|
|
3
|
+
from pydantic import BaseModel, Field, model_validator
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
# ============================================================================
|
|
7
|
+
# Enums
|
|
8
|
+
# ============================================================================
|
|
9
|
+
|
|
10
|
+
class CreditTier(str, Enum):
|
|
11
|
+
PRIME = "PRIME"
|
|
12
|
+
NEAR_PRIME = "NEAR_PRIME"
|
|
13
|
+
SUBPRIME = "SUBPRIME"
|
|
14
|
+
DEEP_SUBPRIME = "DEEP_SUBPRIME"
|
|
15
|
+
UNKNOWN = "UNKNOWN"
|
|
16
|
+
|
|
17
|
+
class ProductType(str, Enum):
|
|
18
|
+
INSTALLMENT_LOAN = "INSTALLMENT_LOAN"
|
|
19
|
+
LINE_OF_CREDIT = "LINE_OF_CREDIT"
|
|
20
|
+
SHORT_TERM_CREDIT = "SHORT_TERM_CREDIT"
|
|
21
|
+
BNPL = "BNPL"
|
|
22
|
+
MORTGAGE = "MORTGAGE"
|
|
23
|
+
AUTO_LOAN = "AUTO_LOAN"
|
|
24
|
+
AUTO_REFINANCE = "AUTO_REFINANCE"
|
|
25
|
+
STUDENT_LOAN = "STUDENT_LOAN"
|
|
26
|
+
STUDENT_LOAN_REFINANCE = "STUDENT_LOAN_REFINANCE"
|
|
27
|
+
BUSINESS_LOAN = "BUSINESS_LOAN"
|
|
28
|
+
BUSINESS_BANKING = "BUSINESS_BANKING"
|
|
29
|
+
BUSINESS_CREDIT_CARD = "BUSINESS_CREDIT_CARD"
|
|
30
|
+
PERSONAL_BANKING = "PERSONAL_BANKING"
|
|
31
|
+
SAVINGS_ACCOUNT = "SAVINGS_ACCOUNT"
|
|
32
|
+
PERSONAL_CREDIT_CARD = "PERSONAL_CREDIT_CARD"
|
|
33
|
+
|
|
34
|
+
class ApplicationType(str, Enum):
|
|
35
|
+
PERSONAL = "PERSONAL"
|
|
36
|
+
BUSINESS = "BUSINESS"
|
|
37
|
+
|
|
38
|
+
class ApplicationStatus(str, Enum):
|
|
39
|
+
DRAFT = "DRAFT"
|
|
40
|
+
SUBMITTED = "SUBMITTED"
|
|
41
|
+
IN_REVIEW = "IN_REVIEW"
|
|
42
|
+
ACTION_REQUIRED = "ACTION_REQUIRED"
|
|
43
|
+
APPROVED = "APPROVED"
|
|
44
|
+
DECLINED = "DECLINED"
|
|
45
|
+
CLOSED = "CLOSED"
|
|
46
|
+
|
|
47
|
+
class ProviderStatus(str, Enum):
|
|
48
|
+
PENDING = "PENDING"
|
|
49
|
+
SUBMITTED = "SUBMITTED"
|
|
50
|
+
REFERRED = "REFERRED"
|
|
51
|
+
ERROR = "ERROR"
|
|
52
|
+
RECEIVED = "RECEIVED"
|
|
53
|
+
|
|
54
|
+
# ============================================================================
|
|
55
|
+
# Common & Base Types
|
|
56
|
+
# ============================================================================
|
|
57
|
+
|
|
58
|
+
class Money(BaseModel):
|
|
59
|
+
amount: float
|
|
60
|
+
currency: Optional[str] = None
|
|
61
|
+
|
|
62
|
+
class Resource(BaseModel):
|
|
63
|
+
mime_type: Optional[str] = Field(None, alias="mimeType")
|
|
64
|
+
text: Optional[str] = None
|
|
65
|
+
|
|
66
|
+
class ContentItem(BaseModel):
|
|
67
|
+
type: Optional[str] = None
|
|
68
|
+
resource: Optional[Resource] = None
|
|
69
|
+
text: Optional[str] = None
|
|
70
|
+
|
|
71
|
+
class ToolResult(BaseModel):
|
|
72
|
+
content: List[ContentItem] = []
|
|
73
|
+
structured_content: Optional[Any] = Field(None, alias="structuredContent")
|
|
74
|
+
request_id: Optional[str] = Field(None, alias="requestId")
|
|
75
|
+
|
|
76
|
+
class SecureLendConfig(BaseModel):
|
|
77
|
+
api_key: Optional[str] = Field(None, alias="apiKey")
|
|
78
|
+
server_url: Optional[str] = Field(None, alias="serverUrl")
|
|
79
|
+
|
|
80
|
+
# ============================================================================
|
|
81
|
+
# Loan Comparison
|
|
82
|
+
# ============================================================================
|
|
83
|
+
|
|
84
|
+
class PersonalLoanSearchParams(BaseModel):
|
|
85
|
+
loan_amount: float = Field(..., alias="loanAmount", description="The desired loan amount in USD (e.g., 25000).")
|
|
86
|
+
purpose: Literal["debt_consolidation", "home_improvement", "major_purchase", "medical", "vacation", "other"] = Field(..., description="The purpose of the loan.")
|
|
87
|
+
credit_score: Optional[int] = Field(None, alias="creditScore", description="The applicant's estimated credit score (300-850).")
|
|
88
|
+
monthly_income: Optional[float] = Field(None, alias="monthlyIncome", description="Applicant's gross monthly income in USD.")
|
|
89
|
+
employment_status: Optional[Literal["employed", "self_employed", "retired", "unemployed"]] = Field(None, alias="employmentStatus", description="The applicant's employment status.")
|
|
90
|
+
state: Optional[str] = Field(None, description="The applicant's state of residence (2-letter code).")
|
|
91
|
+
|
|
92
|
+
class BusinessLoanSearchParams(BaseModel):
|
|
93
|
+
loan_amount: float = Field(..., alias="loanAmount", description="The desired loan amount, e.g., 50000 for $50,000.")
|
|
94
|
+
purpose: str = Field(..., description="The reason for the loan, e.g., 'working capital'.")
|
|
95
|
+
annual_revenue: Optional[float] = Field(None, alias="annualRevenue", description="The business's gross annual revenue in USD.")
|
|
96
|
+
industry: Optional[str] = Field(None, description="The industry the business operates in, e.g., 'technology'.")
|
|
97
|
+
state: Optional[str] = Field(None, description="The state where the business is located (2-letter code).")
|
|
98
|
+
|
|
99
|
+
class MortgageSearchParams(BaseModel):
|
|
100
|
+
loan_amount: float = Field(..., alias="loanAmount", description="The desired mortgage loan amount in USD.")
|
|
101
|
+
home_price: Optional[float] = Field(None, alias="homePrice", description="The purchase price of the home in USD.")
|
|
102
|
+
down_payment: Optional[float] = Field(None, alias="downPayment", description="The amount of the down payment in USD.")
|
|
103
|
+
credit_score: Optional[int] = Field(None, alias="creditScore", description="The applicant's estimated credit score (500-850).")
|
|
104
|
+
loan_type: Literal["conventional", "fha", "va", "jumbo", "refinance"] = Field(..., alias="loanType", description="The type of mortgage loan.")
|
|
105
|
+
property_type: Optional[Literal["primary", "secondary", "investment"]] = Field(None, alias="propertyType", description="The intended use of the property.")
|
|
106
|
+
state: Optional[str] = Field(None, description="The state where the property is located (2-letter code).")
|
|
107
|
+
|
|
108
|
+
class AutoLoanSearchParams(BaseModel):
|
|
109
|
+
loan_amount: float = Field(..., alias="loanAmount", description="The desired auto loan amount in USD.")
|
|
110
|
+
credit_score: Optional[int] = Field(None, alias="creditScore", description="The applicant's estimated credit score (300-850).")
|
|
111
|
+
is_new: bool = Field(..., alias="isNew", description="Specifies if the vehicle is new (true) or used (false).")
|
|
112
|
+
state: Optional[str] = Field(None, description="The applicant's state of residence (2-letter code).")
|
|
113
|
+
|
|
114
|
+
class StudentLoanSearchParams(BaseModel):
|
|
115
|
+
loan_amount: float = Field(..., alias="loanAmount", description="The total amount needed for the student loan in USD.")
|
|
116
|
+
credit_score: Optional[int] = Field(None, alias="creditScore", description="The student's estimated credit score (300-850).")
|
|
117
|
+
co_signer_credit_score: Optional[int] = Field(None, alias="coSignerCreditScore", description="The co-signer's estimated credit score (300-850), if applicable.")
|
|
118
|
+
degree_type: Literal["undergraduate", "graduate", "mba", "medical", "law"] = Field(..., alias="degreeType", description="The type of degree the loan is for.")
|
|
119
|
+
state: Optional[str] = Field(None, description="The student's state of residence (2-letter code).")
|
|
120
|
+
|
|
121
|
+
class Lender(BaseModel):
|
|
122
|
+
id: str
|
|
123
|
+
name: str
|
|
124
|
+
type: str
|
|
125
|
+
logo_url: Optional[str] = Field(None, alias="logoUrl")
|
|
126
|
+
|
|
127
|
+
class Product(BaseModel):
|
|
128
|
+
name: str
|
|
129
|
+
type: str
|
|
130
|
+
description: Optional[str] = None
|
|
131
|
+
|
|
132
|
+
class InterestRate(BaseModel):
|
|
133
|
+
type: Literal["fixed", "variable"]
|
|
134
|
+
rate: Optional[float] = None
|
|
135
|
+
apr: float
|
|
136
|
+
|
|
137
|
+
class Payment(BaseModel):
|
|
138
|
+
frequency: Optional[str] = None
|
|
139
|
+
amount: Money
|
|
140
|
+
|
|
141
|
+
class Repayment(BaseModel):
|
|
142
|
+
total_repayment_amount: float = Field(..., alias="totalRepaymentAmount")
|
|
143
|
+
cost_of_financing: float = Field(..., alias="costOfFinancing")
|
|
144
|
+
|
|
145
|
+
class LoanTerms(BaseModel):
|
|
146
|
+
amount: Money
|
|
147
|
+
interest_rate: InterestRate = Field(..., alias="interestRate")
|
|
148
|
+
term_months: int = Field(..., alias="termMonths")
|
|
149
|
+
payment: Payment
|
|
150
|
+
total_cost: Optional[Money] = Field(None, alias="totalCost")
|
|
151
|
+
repayment: Optional[Repayment] = None
|
|
152
|
+
|
|
153
|
+
class OriginationFee(BaseModel):
|
|
154
|
+
amount: Optional[Money] = None
|
|
155
|
+
percentage: Optional[float] = None
|
|
156
|
+
|
|
157
|
+
class LoanFees(BaseModel):
|
|
158
|
+
origination: Optional[OriginationFee] = None
|
|
159
|
+
|
|
160
|
+
class Matching(BaseModel):
|
|
161
|
+
approval_probability: Optional[float] = Field(None, alias="approvalProbability")
|
|
162
|
+
match_score: Optional[float] = Field(None, alias="matchScore")
|
|
163
|
+
match_reasons: Optional[List[str]] = Field(None, alias="matchReasons")
|
|
164
|
+
|
|
165
|
+
class FundingSpeed(BaseModel):
|
|
166
|
+
description: str
|
|
167
|
+
|
|
168
|
+
class Process(BaseModel):
|
|
169
|
+
application_method: str = Field(..., alias="applicationMethod")
|
|
170
|
+
application_url: Optional[str] = Field(None, alias="applicationUrl")
|
|
171
|
+
funding_speed: Optional[FundingSpeed] = Field(None, alias="fundingSpeed")
|
|
172
|
+
|
|
173
|
+
class LoanOffer(BaseModel):
|
|
174
|
+
offer_id: str = Field(..., alias="offerId")
|
|
175
|
+
lender: Lender
|
|
176
|
+
product: Product
|
|
177
|
+
terms: LoanTerms
|
|
178
|
+
fees: Optional[LoanFees] = None
|
|
179
|
+
matching: Optional[Matching] = None
|
|
180
|
+
process: Optional[Process] = None
|
|
181
|
+
lender_info: Optional[str] = Field(None, alias="lenderInfo")
|
|
182
|
+
badges: Optional[List[str]] = None
|
|
183
|
+
is_secure_lend_tenant: Optional[bool] = Field(None, alias="isSecureLendTenant")
|
|
184
|
+
|
|
185
|
+
class LoanComparisonSummary(BaseModel):
|
|
186
|
+
total_offers: int = Field(..., alias="totalOffers")
|
|
187
|
+
best_rate: float = Field(..., alias="bestRate")
|
|
188
|
+
best_approval_probability: Optional[float] = Field(None, alias="bestApprovalProbability")
|
|
189
|
+
fastest_funding: Optional[str] = Field(None, alias="fastestFunding")
|
|
190
|
+
|
|
191
|
+
class Metadata(BaseModel):
|
|
192
|
+
query_id: str = Field(..., alias="queryId")
|
|
193
|
+
timestamp: str
|
|
194
|
+
session_id: Optional[str] = Field(None, alias="sessionId")
|
|
195
|
+
|
|
196
|
+
class LoanComparisonResponse(BaseModel):
|
|
197
|
+
offers: List[LoanOffer]
|
|
198
|
+
summary: LoanComparisonSummary
|
|
199
|
+
search_criteria: Optional[Union[PersonalLoanSearchParams, BusinessLoanSearchParams, MortgageSearchParams, AutoLoanSearchParams, StudentLoanSearchParams]] = Field(None, alias="searchCriteria")
|
|
200
|
+
metadata: Metadata
|
|
201
|
+
widget: Optional[str] = None
|
|
202
|
+
|
|
203
|
+
# ============================================================================
|
|
204
|
+
# Banking & Credit Cards
|
|
205
|
+
# ============================================================================
|
|
206
|
+
|
|
207
|
+
class BusinessBankingSearchSchema(BaseModel):
|
|
208
|
+
industry: Optional[str] = Field(None, description="The business's industry.")
|
|
209
|
+
monthly_transactions: Optional[int] = Field(None, alias="monthlyTransactions", description="Estimated number of monthly transactions.")
|
|
210
|
+
|
|
211
|
+
class BusinessBankingOffer(BaseModel):
|
|
212
|
+
account_id: str = Field(..., alias="accountId")
|
|
213
|
+
name: str
|
|
214
|
+
issuer: str
|
|
215
|
+
image_url: str = Field(..., alias="imageUrl")
|
|
216
|
+
rating: float
|
|
217
|
+
best_for: str = Field(..., alias="bestFor")
|
|
218
|
+
apy: str
|
|
219
|
+
interest_rate: Optional[str] = Field(None, alias="interestRate")
|
|
220
|
+
monthly_fee: str = Field(..., alias="monthlyFee")
|
|
221
|
+
bonus: str
|
|
222
|
+
|
|
223
|
+
class BusinessBankingComparisonResponse(BaseModel):
|
|
224
|
+
offers: List[BusinessBankingOffer]
|
|
225
|
+
search_criteria: Optional[BusinessBankingSearchSchema] = Field(None, alias="searchCriteria")
|
|
226
|
+
metadata: Metadata
|
|
227
|
+
widget: Optional[str] = None
|
|
228
|
+
|
|
229
|
+
class PersonalBankingSearchSchema(BaseModel):
|
|
230
|
+
features: Optional[List[str]] = Field(None, description="Desired account features.")
|
|
231
|
+
|
|
232
|
+
class PersonalBankingOffer(BaseModel):
|
|
233
|
+
account_id: str = Field(..., alias="accountId")
|
|
234
|
+
name: str
|
|
235
|
+
issuer: str
|
|
236
|
+
image_url: str = Field(..., alias="imageUrl")
|
|
237
|
+
rating: float
|
|
238
|
+
best_for: str = Field(..., alias="bestFor")
|
|
239
|
+
apy: str
|
|
240
|
+
interest_rate: Optional[str] = Field(None, alias="interestRate")
|
|
241
|
+
bonus: str
|
|
242
|
+
monthly_fee: str = Field(..., alias="monthlyFee")
|
|
243
|
+
overdraft_rating: float = Field(..., alias="overdraftRating")
|
|
244
|
+
|
|
245
|
+
class PersonalBankingComparisonResponse(BaseModel):
|
|
246
|
+
offers: List[PersonalBankingOffer]
|
|
247
|
+
search_criteria: Optional[PersonalBankingSearchSchema] = Field(None, alias="searchCriteria")
|
|
248
|
+
metadata: Metadata
|
|
249
|
+
widget: Optional[str] = None
|
|
250
|
+
|
|
251
|
+
class SavingsSearchSchema(BaseModel):
|
|
252
|
+
initial_deposit: Optional[float] = Field(None, alias="initialDeposit", description="The initial deposit amount in USD.")
|
|
253
|
+
|
|
254
|
+
class SavingsOffer(BaseModel):
|
|
255
|
+
account_id: str = Field(..., alias="accountId")
|
|
256
|
+
name: str
|
|
257
|
+
issuer: str
|
|
258
|
+
image_url: str = Field(..., alias="imageUrl")
|
|
259
|
+
rating: float
|
|
260
|
+
apy: str
|
|
261
|
+
interest_rate: Optional[str] = Field(None, alias="interestRate")
|
|
262
|
+
min_balance_for_apy: str = Field(..., alias="minBalanceForApy")
|
|
263
|
+
monthly_fee: str = Field(..., alias="monthlyFee")
|
|
264
|
+
|
|
265
|
+
class SavingsAccountComparisonResponse(BaseModel):
|
|
266
|
+
offers: List[SavingsOffer]
|
|
267
|
+
search_criteria: Optional[SavingsSearchSchema] = Field(None, alias="searchCriteria")
|
|
268
|
+
metadata: Metadata
|
|
269
|
+
widget: Optional[str] = None
|
|
270
|
+
|
|
271
|
+
class BusinessCreditCardSearchParams(BaseModel):
|
|
272
|
+
credit_score: Optional[int] = Field(None, alias="creditScore", description="The applicant's estimated credit score (300-850).")
|
|
273
|
+
annual_revenue: Optional[float] = Field(None, alias="annualRevenue", description="The business's annual revenue in USD.")
|
|
274
|
+
business_age_in_years: Optional[float] = Field(None, alias="businessAgeInYears", description="The age of the business in years.")
|
|
275
|
+
|
|
276
|
+
class BusinessCreditCardTerms(BaseModel):
|
|
277
|
+
apr: float
|
|
278
|
+
annual_fee: float = Field(..., alias="annualFee")
|
|
279
|
+
credit_limit: Money = Field(..., alias="creditLimit")
|
|
280
|
+
|
|
281
|
+
class BusinessCreditCardOffer(BaseModel):
|
|
282
|
+
card_id: str = Field(..., alias="cardId")
|
|
283
|
+
name: str
|
|
284
|
+
issuer: str
|
|
285
|
+
image_url: str = Field(..., alias="imageUrl")
|
|
286
|
+
rating: float
|
|
287
|
+
credit_score_range: str = Field(..., alias="creditScoreRange")
|
|
288
|
+
welcome_bonus: str = Field(..., alias="welcomeBonus")
|
|
289
|
+
terms: BusinessCreditCardTerms
|
|
290
|
+
|
|
291
|
+
class BusinessCreditCardComparisonResponse(BaseModel):
|
|
292
|
+
offers: List[BusinessCreditCardOffer]
|
|
293
|
+
search_criteria: Optional[BusinessCreditCardSearchParams] = Field(None, alias="searchCriteria")
|
|
294
|
+
metadata: Metadata
|
|
295
|
+
widget: Optional[str] = None
|
|
296
|
+
|
|
297
|
+
class PersonalCreditCardSearchSchema(BaseModel):
|
|
298
|
+
credit_score: Optional[int] = Field(None, alias="creditScore", description="The applicant's estimated credit score (300-850).")
|
|
299
|
+
rewards_type: Optional[Literal["cash_back", "travel", "points"]] = Field(None, alias="rewardsType", description="Preferred rewards type.")
|
|
300
|
+
|
|
301
|
+
class PersonalCreditCardAPRs(BaseModel):
|
|
302
|
+
purchase: float
|
|
303
|
+
balance_transfer: float = Field(..., alias="balanceTransfer")
|
|
304
|
+
penalty: Optional[float] = None
|
|
305
|
+
|
|
306
|
+
class PersonalCreditCardFees(BaseModel):
|
|
307
|
+
annual: float
|
|
308
|
+
|
|
309
|
+
class PersonalCreditCardTerms(BaseModel):
|
|
310
|
+
apr: PersonalCreditCardAPRs
|
|
311
|
+
fees: PersonalCreditCardFees
|
|
312
|
+
|
|
313
|
+
class PersonalCreditCardOffer(BaseModel):
|
|
314
|
+
card_id: str = Field(..., alias="cardId")
|
|
315
|
+
name: str
|
|
316
|
+
issuer: str
|
|
317
|
+
image_url: str = Field(..., alias="imageUrl")
|
|
318
|
+
rating: float
|
|
319
|
+
best_for: str = Field(..., alias="bestFor")
|
|
320
|
+
rewards_rate: str = Field(..., alias="rewardsRate")
|
|
321
|
+
intro_offer: str = Field(..., alias="introOffer")
|
|
322
|
+
recommended_credit_score: str = Field(..., alias="recommendedCreditScore")
|
|
323
|
+
terms: PersonalCreditCardTerms
|
|
324
|
+
application_url: Optional[str] = Field(None, alias="applicationUrl")
|
|
325
|
+
|
|
326
|
+
class PersonalCreditCardComparisonResponse(BaseModel):
|
|
327
|
+
offers: List[PersonalCreditCardOffer]
|
|
328
|
+
search_criteria: Optional[PersonalCreditCardSearchSchema] = Field(None, alias="searchCriteria")
|
|
329
|
+
metadata: Metadata
|
|
330
|
+
widget: Optional[str] = None
|
|
331
|
+
|
|
332
|
+
# ============================================================================
|
|
333
|
+
# Financial Calculators
|
|
334
|
+
# ============================================================================
|
|
335
|
+
|
|
336
|
+
class LoanPaymentParams(BaseModel):
|
|
337
|
+
loan_amount: float = Field(..., alias="loanAmount")
|
|
338
|
+
interest_rate: float = Field(..., alias="interestRate")
|
|
339
|
+
loan_term_in_months: int = Field(..., alias="loanTermInMonths")
|
|
340
|
+
|
|
341
|
+
class LoanCalculationResponse(BaseModel):
|
|
342
|
+
monthly_payment: float = Field(..., alias="monthlyPayment")
|
|
343
|
+
total_payment: float = Field(..., alias="totalPayment")
|
|
344
|
+
total_interest: float = Field(..., alias="totalInterest")
|
|
345
|
+
widget: Optional[str] = None
|
|
346
|
+
|
|
347
|
+
class MortgagePaymentParams(BaseModel):
|
|
348
|
+
property_value: float = Field(..., alias="propertyValue")
|
|
349
|
+
down_payment: float = Field(..., alias="downPayment")
|
|
350
|
+
interest_rate: float = Field(..., alias="interestRate")
|
|
351
|
+
loan_term_in_years: int = Field(..., alias="loanTermInYears")
|
|
352
|
+
property_tax_rate: float = Field(..., alias="propertyTaxRate")
|
|
353
|
+
home_insurance: float = Field(..., alias="homeInsurance")
|
|
354
|
+
|
|
355
|
+
class MortgageCalculationResponse(BaseModel):
|
|
356
|
+
loan_amount: float = Field(..., alias="loanAmount")
|
|
357
|
+
principal_and_interest: float = Field(..., alias="principalAndInterest")
|
|
358
|
+
monthly_property_tax: float = Field(..., alias="monthlyPropertyTax")
|
|
359
|
+
monthly_home_insurance: float = Field(..., alias="monthlyHomeInsurance")
|
|
360
|
+
total_monthly_payment: float = Field(..., alias="totalMonthlyPayment")
|
|
361
|
+
widget: Optional[str] = None
|
|
362
|
+
|
|
363
|
+
class LeaseVsPurchaseParams(BaseModel):
|
|
364
|
+
purchase_price: float = Field(..., alias="purchasePrice")
|
|
365
|
+
down_payment: float = Field(..., alias="downPayment")
|
|
366
|
+
loan_term_in_months: int = Field(..., alias="loanTermInMonths")
|
|
367
|
+
interest_rate: float = Field(..., alias="interestRate")
|
|
368
|
+
sales_tax_rate: float = Field(..., alias="salesTaxRate")
|
|
369
|
+
lease_term_in_months: int = Field(..., alias="leaseTermInMonths")
|
|
370
|
+
monthly_lease_payment: float = Field(..., alias="monthlyLeasePayment")
|
|
371
|
+
money_factor: float = Field(..., alias="moneyFactor")
|
|
372
|
+
acquisition_fee: float = Field(..., alias="acquisitionFee")
|
|
373
|
+
security_deposit: float = Field(..., alias="securityDeposit")
|
|
374
|
+
residual_value_percentage: float = Field(..., alias="residualValuePercentage")
|
|
375
|
+
expected_ownership_in_months: int = Field(..., alias="expectedOwnershipInMonths")
|
|
376
|
+
|
|
377
|
+
class LeaseVsPurchaseResponse(BaseModel):
|
|
378
|
+
purchase_analysis: Dict[str, Any] = Field(..., alias="purchaseAnalysis")
|
|
379
|
+
lease_analysis: Dict[str, Any] = Field(..., alias="leaseAnalysis")
|
|
380
|
+
comparison: Dict[str, Any]
|
|
381
|
+
widget: Optional[str] = None
|
|
382
|
+
|
|
383
|
+
# ============================================================================
|
|
384
|
+
# Application Management
|
|
385
|
+
# ============================================================================
|
|
386
|
+
|
|
387
|
+
class PersonalApplicant(BaseModel):
|
|
388
|
+
first_name: str = Field(..., alias="firstName", description="The applicant's first name.")
|
|
389
|
+
last_name: str = Field(..., alias="lastName", description="The applicant's last name.")
|
|
390
|
+
email: str = Field(..., description="The applicant's email address.")
|
|
391
|
+
phone: Optional[str] = Field(None, description="The applicant's phone number.")
|
|
392
|
+
|
|
393
|
+
class Provider(BaseModel):
|
|
394
|
+
provider_id: str = Field(..., alias="providerId", description="The ID of the selected provider.")
|
|
395
|
+
provider_name: str = Field(..., alias="providerName", description="The name of the selected provider.")
|
|
396
|
+
|
|
397
|
+
class GetOfferParams(BaseModel):
|
|
398
|
+
product_type: ProductType = Field(..., alias="productType", description="The type of financial product being applied for.")
|
|
399
|
+
applicant: PersonalApplicant = Field(..., description="Personal details of the applicant.")
|
|
400
|
+
application_data: Dict[str, Any] = Field(..., alias="applicationData", description="The original search parameters or form data for the application.")
|
|
401
|
+
provider: Provider = Field(..., description="The selected provider to submit the application to.")
|
|
402
|
+
|
|
403
|
+
class GetMultipleOffersParams(BaseModel):
|
|
404
|
+
product_type: ProductType = Field(..., alias="productType", description="The type of financial product being applied for.")
|
|
405
|
+
applicant: PersonalApplicant = Field(..., description="Personal details of the applicant.")
|
|
406
|
+
application_data: Dict[str, Any] = Field(..., alias="applicationData", description="The original search parameters or form data for the application.")
|
|
407
|
+
providers: List[Provider] = Field(..., min_length=1, description="The list of selected providers to submit the application to.")
|
|
408
|
+
|
|
409
|
+
class ApplicationProvider(BaseModel):
|
|
410
|
+
provider_id: str = Field(..., alias="providerId")
|
|
411
|
+
provider_name: str = Field(..., alias="providerName")
|
|
412
|
+
status: ProviderStatus
|
|
413
|
+
submitted_at: Optional[str] = Field(None, alias="submittedAt")
|
|
414
|
+
provider_application_id: Optional[str] = Field(None, alias="providerApplicationId")
|
|
415
|
+
referral_url: Optional[str] = Field(None, alias="referralUrl")
|
|
416
|
+
error: Optional[str] = None
|
|
417
|
+
|
|
418
|
+
class PersonalApplication(BaseModel):
|
|
419
|
+
id: str
|
|
420
|
+
created_at: str = Field(..., alias="createdAt")
|
|
421
|
+
updated_at: str = Field(..., alias="updatedAt")
|
|
422
|
+
application_type: ApplicationType = Field(..., alias="applicationType")
|
|
423
|
+
product_type: Union[ProductType, str] = Field(..., alias="productType")
|
|
424
|
+
status: ApplicationStatus
|
|
425
|
+
applicant: PersonalApplicant
|
|
426
|
+
application_data: Dict[str, Any] = Field(..., alias="applicationData")
|
|
427
|
+
providers: List[ApplicationProvider]
|
|
428
|
+
email: str
|
|
429
|
+
widget: Optional[str] = None
|
|
430
|
+
|
|
431
|
+
class DisplayOfferFormParams(BaseModel):
|
|
432
|
+
session_id: Optional[str] = Field(None, alias="sessionId")
|
|
433
|
+
offer_id: Optional[str] = Field(None, alias="offerId")
|
|
434
|
+
|
|
435
|
+
AnyOffer = Union[LoanOffer, BusinessCreditCardOffer, BusinessBankingOffer, PersonalBankingOffer, SavingsOffer, PersonalCreditCardOffer]
|
|
436
|
+
|
|
437
|
+
class DisplayOfferFormResponse(BaseModel):
|
|
438
|
+
offer: AnyOffer
|
|
439
|
+
all_offers: List[AnyOffer] = Field(..., alias="allOffers")
|
|
440
|
+
application_data: Dict[str, Any] = Field(..., alias="applicationData")
|
|
441
|
+
product_type: str = Field(..., alias="productType")
|
|
442
|
+
widget: Optional[str] = None
|
|
443
|
+
|
|
444
|
+
class TrackOfferStatusParams(BaseModel):
|
|
445
|
+
application_id: Optional[str] = Field(None, alias="applicationId")
|
|
446
|
+
email: Optional[str] = None
|
|
447
|
+
|
|
448
|
+
@model_validator(mode="after")
|
|
449
|
+
def check_application_id_or_email(self) -> "TrackOfferStatusParams":
|
|
450
|
+
if not self.application_id and not self.email:
|
|
451
|
+
raise ValueError("Either applicationId or email must be provided.")
|
|
452
|
+
return self
|
|
453
|
+
|
|
454
|
+
class TrackOfferStatusResponse(BaseModel):
|
|
455
|
+
applications: List[PersonalApplication]
|
|
456
|
+
widget: Optional[str] = None
|
|
457
|
+
|
|
458
|
+
class DisplayUploadDocumentsFormParams(BaseModel):
|
|
459
|
+
application_id: Optional[str] = Field(None, alias="applicationId")
|
|
460
|
+
|
|
461
|
+
class DisplayUploadDocumentsFormResponse(BaseModel):
|
|
462
|
+
widget: Optional[str] = None
|
|
463
|
+
|
|
464
|
+
class SubmitDocumentsParams(BaseModel):
|
|
465
|
+
application_id: str = Field(..., alias="applicationId")
|
|
466
|
+
file_name: str = Field(..., alias="fileName")
|
|
467
|
+
document_type: str = Field(..., alias="documentType")
|
|
468
|
+
|
|
469
|
+
class SubmitDocumentsResponse(BaseModel):
|
|
470
|
+
success: bool
|
|
471
|
+
message: str
|
|
472
|
+
upload_url: Optional[str] = Field(None, alias="uploadUrl")
|
|
473
|
+
upload_fields: Optional[Dict[str, str]] = Field(None, alias="uploadFields")
|
|
474
|
+
document_id: Optional[str] = Field(None, alias="documentId")
|
|
475
|
+
widget: Optional[str] = None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# This file makes the `utils` directory a Python package.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class SecureLendError(Exception):
|
|
2
|
+
"""Base exception for all SecureLend SDK errors."""
|
|
3
|
+
|
|
4
|
+
def __init__(self, message: str, error_type: str, details: any = None):
|
|
5
|
+
super().__init__(message)
|
|
6
|
+
self.type = error_type
|
|
7
|
+
self.details = details
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AuthenticationError(SecureLendError):
|
|
11
|
+
def __init__(self, message: str):
|
|
12
|
+
super().__init__(message, "authentication_error")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RateLimitError(SecureLendError):
|
|
16
|
+
def __init__(self, message: str, retry_after: int | None = None):
|
|
17
|
+
super().__init__(message, "rate_limit_error")
|
|
18
|
+
self.retry_after = retry_after
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ValidationError(SecureLendError):
|
|
22
|
+
def __init__(self, message: str, errors: any = None):
|
|
23
|
+
super().__init__(message, "validation_error", errors)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class NotFoundError(SecureLendError):
|
|
27
|
+
def __init__(self, message: str):
|
|
28
|
+
super().__init__(message, "not_found")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class NetworkError(SecureLendError):
|
|
32
|
+
def __init__(self, message: str):
|
|
33
|
+
super().__init__(message, "network_error")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ServerError(SecureLendError):
|
|
37
|
+
def __init__(self, message: str):
|
|
38
|
+
super().__init__(message, "server_error")
|
securelend/utils/mcp.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from contextlib import AsyncExitStack
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from mcp import ClientSession, McpError
|
|
6
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
7
|
+
|
|
8
|
+
from ..utils.errors import (
|
|
9
|
+
AuthenticationError,
|
|
10
|
+
NetworkError,
|
|
11
|
+
RateLimitError,
|
|
12
|
+
ServerError,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MCPClient:
|
|
17
|
+
def __init__(self, *, api_key: str, mcp_url: str):
|
|
18
|
+
self._api_key = api_key
|
|
19
|
+
self._mcp_url = mcp_url
|
|
20
|
+
self._debug = False
|
|
21
|
+
self._session: Optional[ClientSession] = None
|
|
22
|
+
self._exit_stack = AsyncExitStack()
|
|
23
|
+
self._reconnect_required = False
|
|
24
|
+
|
|
25
|
+
async def _get_session(self) -> ClientSession:
|
|
26
|
+
if self._session is None or self._reconnect_required:
|
|
27
|
+
await self.connect()
|
|
28
|
+
self._reconnect_required = False
|
|
29
|
+
assert self._session is not None
|
|
30
|
+
return self._session
|
|
31
|
+
|
|
32
|
+
async def connect(self) -> None:
|
|
33
|
+
if self._session:
|
|
34
|
+
await self.close()
|
|
35
|
+
|
|
36
|
+
self._exit_stack = AsyncExitStack()
|
|
37
|
+
headers = {
|
|
38
|
+
"User-Agent": f"securelend-python/{__import__('securelend').__version__}",
|
|
39
|
+
}
|
|
40
|
+
if self._api_key:
|
|
41
|
+
headers["Authorization"] = f"Bearer {self._api_key}"
|
|
42
|
+
|
|
43
|
+
if self._debug:
|
|
44
|
+
print(f"[SecureLend SDK] Connecting to MCP server at {self._mcp_url}")
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
transport_context = streamablehttp_client(
|
|
48
|
+
url=self._mcp_url, headers=headers
|
|
49
|
+
)
|
|
50
|
+
read_stream, write_stream, _ = await self._exit_stack.enter_async_context(
|
|
51
|
+
transport_context
|
|
52
|
+
)
|
|
53
|
+
session_context = ClientSession(read_stream, write_stream)
|
|
54
|
+
self._session = await self._exit_stack.enter_async_context(session_context)
|
|
55
|
+
await self._session.initialize()
|
|
56
|
+
except httpx.HTTPStatusError as e:
|
|
57
|
+
if e.response.status_code in (401, 403):
|
|
58
|
+
raise AuthenticationError("Authentication failed.") from e
|
|
59
|
+
if e.response.status_code == 429:
|
|
60
|
+
retry_after_header = e.response.headers.get("Retry-After")
|
|
61
|
+
retry_after = None
|
|
62
|
+
if retry_after_header and retry_after_header.isdigit():
|
|
63
|
+
retry_after = int(retry_after_header)
|
|
64
|
+
raise RateLimitError(
|
|
65
|
+
"Rate limit exceeded.", retry_after=retry_after
|
|
66
|
+
) from e
|
|
67
|
+
if e.response.status_code >= 500:
|
|
68
|
+
raise ServerError(f"Server error: {e.response.text}") from e
|
|
69
|
+
raise NetworkError(f"HTTP error: {e}") from e
|
|
70
|
+
except httpx.RequestError as e:
|
|
71
|
+
raise NetworkError(f"Network error: {e}") from e
|
|
72
|
+
except Exception as e:
|
|
73
|
+
raise NetworkError(f"Failed to connect to MCP server: {e}") from e
|
|
74
|
+
|
|
75
|
+
async def call_tool(self, name: str, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
76
|
+
session = await self._get_session()
|
|
77
|
+
try:
|
|
78
|
+
if self._debug:
|
|
79
|
+
print(f"[SecureLend SDK] Calling tool '{name}' with args: {args}")
|
|
80
|
+
|
|
81
|
+
result = await session.call_tool(name, args)
|
|
82
|
+
|
|
83
|
+
content_list = []
|
|
84
|
+
for item in result.content:
|
|
85
|
+
content_item = {"type": item.type, "text": getattr(item, "text", None)}
|
|
86
|
+
if hasattr(item, "resource") and item.resource:
|
|
87
|
+
content_item["resource"] = {
|
|
88
|
+
"mimeType": item.resource.mimeType,
|
|
89
|
+
"text": item.resource.text,
|
|
90
|
+
}
|
|
91
|
+
content_list.append(content_item)
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
"content": content_list,
|
|
95
|
+
"structuredContent": getattr(result, "structuredContent", None),
|
|
96
|
+
"requestId": getattr(result, "requestId", None),
|
|
97
|
+
}
|
|
98
|
+
except McpError as e:
|
|
99
|
+
raise ServerError(f"MCP tool call failed: {e.message}") from e
|
|
100
|
+
|
|
101
|
+
async def close(self):
|
|
102
|
+
if self._session:
|
|
103
|
+
await self._exit_stack.aclose()
|
|
104
|
+
self._session = None
|
|
105
|
+
self._exit_stack = AsyncExitStack()
|
|
106
|
+
|
|
107
|
+
def set_api_key(self, api_key: str) -> None:
|
|
108
|
+
self._api_key = api_key
|
|
109
|
+
self._reconnect_required = True
|
|
110
|
+
|
|
111
|
+
def enable_debug(self) -> None:
|
|
112
|
+
self._debug = True
|
|
113
|
+
|
|
114
|
+
def disable_debug(self) -> None:
|
|
115
|
+
self._debug = False
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: securelend
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SecureLend SDK for Python
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/SecureLend/sdk/tree/main/packages/python
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/SecureLend/sdk/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: mcp>=1.2.0
|
|
13
|
+
Requires-Dist: httpx>=0.27.0
|
|
14
|
+
Requires-Dist: pydantic>=2.0
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.20; extra == "test"
|
|
18
|
+
|
|
19
|
+
# SecureLend SDK for Python [](https://pypi.org/project/securelend/)
|
|
20
|
+
|
|
21
|
+
The official Python SDK for SecureLend - Financial services infrastructure for AI assistants.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install securelend
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import asyncio
|
|
33
|
+
import os
|
|
34
|
+
from securelend import SecureLend
|
|
35
|
+
|
|
36
|
+
async def main():
|
|
37
|
+
# The API server is public and does not require an API key.
|
|
38
|
+
# A key can optionally be provided for usage tracking.
|
|
39
|
+
async with SecureLend(api_key=os.environ.get("SECURELEND_API_KEY")) as securelend:
|
|
40
|
+
response = await securelend.compare_business_loans({
|
|
41
|
+
"loanAmount": 200000,
|
|
42
|
+
"purpose": "equipment",
|
|
43
|
+
"annualRevenue": 1200000,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
print(f"Found {response.summary.total_offers} loan offers.")
|
|
47
|
+
|
|
48
|
+
if response.offers:
|
|
49
|
+
top_offer = response.offers[0]
|
|
50
|
+
apr = top_offer.terms.interest_rate.apr * 100
|
|
51
|
+
print(f"Top offer from {top_offer.lender.name} with {apr:.2f}% APR.")
|
|
52
|
+
|
|
53
|
+
# Calculate payment details for the top offer
|
|
54
|
+
payment_details = await securelend.calculate_loan_payment({
|
|
55
|
+
"loanAmount": top_offer.terms.amount.amount,
|
|
56
|
+
"interestRate": apr,
|
|
57
|
+
"loanTermInMonths": top_offer.terms.term_months,
|
|
58
|
+
})
|
|
59
|
+
print(f" - Estimated Monthly Payment: ${payment_details.monthly_payment:,.2f}")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
asyncio.run(main())
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Documentation
|
|
67
|
+
|
|
68
|
+
- [Full Documentation](https://docs.securelend.ai)
|
|
69
|
+
- [API Reference](https://docs.securelend.ai/api)
|
|
70
|
+
- [Guides](https://docs.securelend.ai/guides)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
securelend/__init__.py,sha256=HIaVtRuOEOiTKDxnYFIKyuwNtuPivfWzp9whktUb-Og,553
|
|
2
|
+
securelend/client.py,sha256=WdmIH_LnBk9F20SPXx0pZ9JJqjkZwjQEUD33EL0uQEs,11660
|
|
3
|
+
securelend/types.py,sha256=-f43TX5oNaJ-16jeoH3S6AMHRO_cVJCV-rYO_bjiLGs,21899
|
|
4
|
+
securelend/utils/__init__.py,sha256=1IwFDtwJ76i1O7_iM4LLGqwgtt11y0PIV0DWubh8nLU,58
|
|
5
|
+
securelend/utils/errors.py,sha256=rPT22bGnPKM8sYubDjI2Mx3VQ_mMGcsYd5pONwMSzLI,1150
|
|
6
|
+
securelend/utils/mcp.py,sha256=1yvEweKcvmrOhd1iHp13uEx67ANLJF1CUY4uElEq_ls,4296
|
|
7
|
+
securelend-0.1.0.dist-info/METADATA,sha256=HgutbST1RKPcLUC7qbY7lA_ezVx4CGtaMblwPoIdVkE,2304
|
|
8
|
+
securelend-0.1.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
9
|
+
securelend-0.1.0.dist-info/top_level.txt,sha256=my2T0PhysHbgmRHqTy7ZaLnHpLFa9o9zB9rJQZWE9BI,11
|
|
10
|
+
securelend-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
securelend
|