From 0dbe188b113a90c1bd781f7e5cd687029987966c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 15:01:38 +0000 Subject: [PATCH 01/12] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index fd0cb7e8..c985d05c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 176 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-c24eebe942f400bff8922a6fbef1ce551ad14f61eb4da21b50d823a62ca42586.yml openapi_spec_hash: b79ed927e625dedff69cea29131a34d9 -config_hash: 693dddc4721eef512d75ab6c60897794 +config_hash: fbc424e01cca916048d63adcadaa8750 From ec268b7426be15578b2eaf92ca81107cbf9084e6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:38:12 +0000 Subject: [PATCH 02/12] chore: update mock server docs --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d68a8975..e758caa0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,8 +88,7 @@ $ pip install ./path-to-wheel-file.whl Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. ```sh -# you will need npm installed -$ npx prism mock path/to/your/openapi.yml +$ ./scripts/mock ``` ```sh From c4a37de9f0f7821e54d05702c4d1d809124fc7bc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 17:49:43 +0000 Subject: [PATCH 03/12] feat(api): Add INTEREST_AND_FEES_PAUSED substatus to financial account --- .stats.yml | 4 ++-- .../financial_accounts/financial_accounts.py | 20 +++++++++++++++++-- src/lithic/types/financial_account.py | 9 ++++++++- .../financial_account_update_status_params.py | 10 +++++++++- .../types/financial_accounts/loan_tape.py | 9 ++++++++- .../types/financial_accounts/statement.py | 9 ++++++++- 6 files changed, 53 insertions(+), 8 deletions(-) diff --git a/.stats.yml b/.stats.yml index c985d05c..2f34650e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 176 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-c24eebe942f400bff8922a6fbef1ce551ad14f61eb4da21b50d823a62ca42586.yml -openapi_spec_hash: b79ed927e625dedff69cea29131a34d9 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-f85b60190db68921a3a877d0dd931670c27933ba1f5031fcdd27365e99adb5c9.yml +openapi_spec_hash: 4828c2dc7543ce2a39774a9921c73c80 config_hash: fbc424e01cca916048d63adcadaa8750 diff --git a/src/lithic/resources/financial_accounts/financial_accounts.py b/src/lithic/resources/financial_accounts/financial_accounts.py index af68c9d5..e42cc408 100644 --- a/src/lithic/resources/financial_accounts/financial_accounts.py +++ b/src/lithic/resources/financial_accounts/financial_accounts.py @@ -319,7 +319,15 @@ def update_status( financial_account_token: str, *, status: Literal["OPEN", "CLOSED", "SUSPENDED", "PENDING"], - substatus: Optional[Literal["CHARGED_OFF_FRAUD", "END_USER_REQUEST", "BANK_REQUEST", "CHARGED_OFF_DELINQUENT"]], + substatus: Optional[ + Literal[ + "CHARGED_OFF_FRAUD", + "END_USER_REQUEST", + "BANK_REQUEST", + "CHARGED_OFF_DELINQUENT", + "INTEREST_AND_FEES_PAUSED", + ] + ], user_defined_status: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -621,7 +629,15 @@ async def update_status( financial_account_token: str, *, status: Literal["OPEN", "CLOSED", "SUSPENDED", "PENDING"], - substatus: Optional[Literal["CHARGED_OFF_FRAUD", "END_USER_REQUEST", "BANK_REQUEST", "CHARGED_OFF_DELINQUENT"]], + substatus: Optional[ + Literal[ + "CHARGED_OFF_FRAUD", + "END_USER_REQUEST", + "BANK_REQUEST", + "CHARGED_OFF_DELINQUENT", + "INTEREST_AND_FEES_PAUSED", + ] + ], user_defined_status: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. diff --git a/src/lithic/types/financial_account.py b/src/lithic/types/financial_account.py index 9c232fa6..92e7ddd4 100644 --- a/src/lithic/types/financial_account.py +++ b/src/lithic/types/financial_account.py @@ -47,7 +47,14 @@ class FinancialAccount(BaseModel): """Status of the financial account""" substatus: Optional[ - Literal["CHARGED_OFF_DELINQUENT", "CHARGED_OFF_FRAUD", "END_USER_REQUEST", "BANK_REQUEST", "DELINQUENT"] + Literal[ + "CHARGED_OFF_DELINQUENT", + "CHARGED_OFF_FRAUD", + "END_USER_REQUEST", + "BANK_REQUEST", + "DELINQUENT", + "INTEREST_AND_FEES_PAUSED", + ] ] = None """Substatus for the financial account""" diff --git a/src/lithic/types/financial_account_update_status_params.py b/src/lithic/types/financial_account_update_status_params.py index cc2d79a8..5680efee 100644 --- a/src/lithic/types/financial_account_update_status_params.py +++ b/src/lithic/types/financial_account_update_status_params.py @@ -13,7 +13,15 @@ class FinancialAccountUpdateStatusParams(TypedDict, total=False): """Status of the financial account""" substatus: Required[ - Optional[Literal["CHARGED_OFF_FRAUD", "END_USER_REQUEST", "BANK_REQUEST", "CHARGED_OFF_DELINQUENT"]] + Optional[ + Literal[ + "CHARGED_OFF_FRAUD", + "END_USER_REQUEST", + "BANK_REQUEST", + "CHARGED_OFF_DELINQUENT", + "INTEREST_AND_FEES_PAUSED", + ] + ] ] """Substatus for the financial account""" diff --git a/src/lithic/types/financial_accounts/loan_tape.py b/src/lithic/types/financial_accounts/loan_tape.py index 6c16a8ca..583f98cd 100644 --- a/src/lithic/types/financial_accounts/loan_tape.py +++ b/src/lithic/types/financial_accounts/loan_tape.py @@ -28,7 +28,14 @@ class AccountStandingFinancialAccountState(BaseModel): """Status of the financial account""" substatus: Optional[ - Literal["CHARGED_OFF_DELINQUENT", "CHARGED_OFF_FRAUD", "END_USER_REQUEST", "BANK_REQUEST", "DELINQUENT"] + Literal[ + "CHARGED_OFF_DELINQUENT", + "CHARGED_OFF_FRAUD", + "END_USER_REQUEST", + "BANK_REQUEST", + "DELINQUENT", + "INTEREST_AND_FEES_PAUSED", + ] ] = None """Substatus for the financial account""" diff --git a/src/lithic/types/financial_accounts/statement.py b/src/lithic/types/financial_accounts/statement.py index 37b8884d..513630fb 100644 --- a/src/lithic/types/financial_accounts/statement.py +++ b/src/lithic/types/financial_accounts/statement.py @@ -25,7 +25,14 @@ class AccountStandingFinancialAccountState(BaseModel): """Status of the financial account""" substatus: Optional[ - Literal["CHARGED_OFF_DELINQUENT", "CHARGED_OFF_FRAUD", "END_USER_REQUEST", "BANK_REQUEST", "DELINQUENT"] + Literal[ + "CHARGED_OFF_DELINQUENT", + "CHARGED_OFF_FRAUD", + "END_USER_REQUEST", + "BANK_REQUEST", + "DELINQUENT", + "INTEREST_AND_FEES_PAUSED", + ] ] = None """Substatus for the financial account""" From 1b49e550b0e6fe3c5203740a9634ee6264046489 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:31:42 +0000 Subject: [PATCH 04/12] chore(internal): add request options to SSE classes --- src/lithic/_legacy_response.py | 3 +++ src/lithic/_response.py | 3 +++ src/lithic/_streaming.py | 11 ++++++++--- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/lithic/_legacy_response.py b/src/lithic/_legacy_response.py index d3758f2b..415c593c 100644 --- a/src/lithic/_legacy_response.py +++ b/src/lithic/_legacy_response.py @@ -214,6 +214,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -224,6 +225,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -237,6 +239,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) diff --git a/src/lithic/_response.py b/src/lithic/_response.py index 083b08df..f19ac6bc 100644 --- a/src/lithic/_response.py +++ b/src/lithic/_response.py @@ -152,6 +152,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -162,6 +163,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -175,6 +177,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) diff --git a/src/lithic/_streaming.py b/src/lithic/_streaming.py index 73afbbec..38d8cb5f 100644 --- a/src/lithic/_streaming.py +++ b/src/lithic/_streaming.py @@ -4,7 +4,7 @@ import json import inspect from types import TracebackType -from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable import httpx @@ -13,6 +13,7 @@ if TYPE_CHECKING: from ._client import Lithic, AsyncLithic + from ._models import FinalRequestOptions _T = TypeVar("_T") @@ -22,7 +23,7 @@ class Stream(Generic[_T]): """Provides the core interface to iterate over a synchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEBytesDecoder def __init__( @@ -31,10 +32,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: Lithic, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() @@ -85,7 +88,7 @@ class AsyncStream(Generic[_T]): """Provides the core interface to iterate over an asynchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEDecoder | SSEBytesDecoder def __init__( @@ -94,10 +97,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: AsyncLithic, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() From 7dae7892de61a765ab4bca8c7af3046d90fe2ff4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 14:09:25 +0000 Subject: [PATCH 05/12] chore(dependencies): require standardwebhooks 1.0.1 this release includes an important bug fix. --- pyproject.toml | 2 +- requirements-dev.lock | 2 +- requirements.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5f325713..b175a6da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ Repository = "https://github.com/lithic-com/lithic-python" [project.optional-dependencies] aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"] -webhooks = ["standardwebhooks"] +webhooks = ["standardwebhooks >= 1.0.1, < 2"] [tool.rye] managed = true diff --git a/requirements-dev.lock b/requirements-dev.lock index 197f41b6..61575831 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -125,7 +125,7 @@ six==1.17.0 # via python-dateutil sniffio==1.3.1 # via lithic -standardwebhooks==1.0.0 +standardwebhooks==1.0.1 # via lithic time-machine==2.19.0 tomli==2.4.0 diff --git a/requirements.lock b/requirements.lock index 01e8660f..3ae4c2cf 100644 --- a/requirements.lock +++ b/requirements.lock @@ -69,7 +69,7 @@ six==1.17.0 # via python-dateutil sniffio==1.3.1 # via lithic -standardwebhooks==1.0.0 +standardwebhooks==1.0.1 # via lithic types-deprecated==1.3.1.20251101 # via standardwebhooks From 5c6bcd740933255291c0fd0b482021c4a50d0e78 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 18:30:15 +0000 Subject: [PATCH 06/12] feat(api): Expose MIL interest schedules and loan tape configuration endpoints --- .stats.yml | 8 +- api.md | 28 + .../resources/financial_accounts/__init__.py | 28 + .../financial_accounts/financial_accounts.py | 64 ++ .../interest_tier_schedule.py | 681 ++++++++++++++++++ .../loan_tape_configuration.py | 167 +++++ .../types/financial_accounts/__init__.py | 6 + .../interest_tier_schedule.py | 27 + .../interest_tier_schedule_create_params.py | 28 + .../interest_tier_schedule_list_params.py | 22 + .../interest_tier_schedule_update_params.py | 20 + .../loan_tape_configuration.py | 28 + .../loan_tape_rebuild_configuration.py | 21 + .../test_interest_tier_schedule.py | 566 +++++++++++++++ .../test_loan_tape_configuration.py | 104 +++ 15 files changed, 1794 insertions(+), 4 deletions(-) create mode 100644 src/lithic/resources/financial_accounts/interest_tier_schedule.py create mode 100644 src/lithic/resources/financial_accounts/loan_tape_configuration.py create mode 100644 src/lithic/types/financial_accounts/interest_tier_schedule.py create mode 100644 src/lithic/types/financial_accounts/interest_tier_schedule_create_params.py create mode 100644 src/lithic/types/financial_accounts/interest_tier_schedule_list_params.py create mode 100644 src/lithic/types/financial_accounts/interest_tier_schedule_update_params.py create mode 100644 src/lithic/types/financial_accounts/loan_tape_configuration.py create mode 100644 src/lithic/types/financial_accounts/loan_tape_rebuild_configuration.py create mode 100644 tests/api_resources/financial_accounts/test_interest_tier_schedule.py create mode 100644 tests/api_resources/financial_accounts/test_loan_tape_configuration.py diff --git a/.stats.yml b/.stats.yml index 2f34650e..258b775f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 176 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-f85b60190db68921a3a877d0dd931670c27933ba1f5031fcdd27365e99adb5c9.yml -openapi_spec_hash: 4828c2dc7543ce2a39774a9921c73c80 -config_hash: fbc424e01cca916048d63adcadaa8750 +configured_endpoints: 182 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-f99894d5b6eda608756c9e5e9868c81c4ce8c74c4d8958370cc3799766a13d65.yml +openapi_spec_hash: 2f364e16b58e5a9759fc9f772cb33f3c +config_hash: 2ee394874b7eb4cbe06f044b7376a6ba diff --git a/api.md b/api.md index 92414f8a..18672599 100644 --- a/api.md +++ b/api.md @@ -420,6 +420,34 @@ Methods: - client.financial_accounts.loan_tapes.retrieve(loan_tape_token, \*, financial_account_token) -> LoanTape - client.financial_accounts.loan_tapes.list(financial_account_token, \*\*params) -> SyncCursorPage[LoanTape] +## LoanTapeConfiguration + +Types: + +```python +from lithic.types.financial_accounts import LoanTapeConfiguration, LoanTapeRebuildConfiguration +``` + +Methods: + +- client.financial_accounts.loan_tape_configuration.retrieve(financial_account_token) -> LoanTapeConfiguration + +## InterestTierSchedule + +Types: + +```python +from lithic.types.financial_accounts import CategoryTier, InterestTierSchedule +``` + +Methods: + +- client.financial_accounts.interest_tier_schedule.create(financial_account_token, \*\*params) -> InterestTierSchedule +- client.financial_accounts.interest_tier_schedule.retrieve(effective_date, \*, financial_account_token) -> InterestTierSchedule +- client.financial_accounts.interest_tier_schedule.update(effective_date, \*, financial_account_token, \*\*params) -> InterestTierSchedule +- client.financial_accounts.interest_tier_schedule.list(financial_account_token, \*\*params) -> SyncSinglePage[InterestTierSchedule] +- client.financial_accounts.interest_tier_schedule.delete(effective_date, \*, financial_account_token) -> None + # Transactions Types: diff --git a/src/lithic/resources/financial_accounts/__init__.py b/src/lithic/resources/financial_accounts/__init__.py index ad35c907..74f0a994 100644 --- a/src/lithic/resources/financial_accounts/__init__.py +++ b/src/lithic/resources/financial_accounts/__init__.py @@ -48,6 +48,22 @@ FinancialTransactionsWithStreamingResponse, AsyncFinancialTransactionsWithStreamingResponse, ) +from .interest_tier_schedule import ( + InterestTierScheduleResource, + AsyncInterestTierScheduleResource, + InterestTierScheduleResourceWithRawResponse, + AsyncInterestTierScheduleResourceWithRawResponse, + InterestTierScheduleResourceWithStreamingResponse, + AsyncInterestTierScheduleResourceWithStreamingResponse, +) +from .loan_tape_configuration import ( + LoanTapeConfigurationResource, + AsyncLoanTapeConfigurationResource, + LoanTapeConfigurationResourceWithRawResponse, + AsyncLoanTapeConfigurationResourceWithRawResponse, + LoanTapeConfigurationResourceWithStreamingResponse, + AsyncLoanTapeConfigurationResourceWithStreamingResponse, +) __all__ = [ "Balances", @@ -80,6 +96,18 @@ "AsyncLoanTapesWithRawResponse", "LoanTapesWithStreamingResponse", "AsyncLoanTapesWithStreamingResponse", + "LoanTapeConfigurationResource", + "AsyncLoanTapeConfigurationResource", + "LoanTapeConfigurationResourceWithRawResponse", + "AsyncLoanTapeConfigurationResourceWithRawResponse", + "LoanTapeConfigurationResourceWithStreamingResponse", + "AsyncLoanTapeConfigurationResourceWithStreamingResponse", + "InterestTierScheduleResource", + "AsyncInterestTierScheduleResource", + "InterestTierScheduleResourceWithRawResponse", + "AsyncInterestTierScheduleResourceWithRawResponse", + "InterestTierScheduleResourceWithStreamingResponse", + "AsyncInterestTierScheduleResourceWithStreamingResponse", "FinancialAccounts", "AsyncFinancialAccounts", "FinancialAccountsWithRawResponse", diff --git a/src/lithic/resources/financial_accounts/financial_accounts.py b/src/lithic/resources/financial_accounts/financial_accounts.py index e42cc408..e26c023c 100644 --- a/src/lithic/resources/financial_accounts/financial_accounts.py +++ b/src/lithic/resources/financial_accounts/financial_accounts.py @@ -62,6 +62,22 @@ FinancialTransactionsWithStreamingResponse, AsyncFinancialTransactionsWithStreamingResponse, ) +from .interest_tier_schedule import ( + InterestTierScheduleResource, + AsyncInterestTierScheduleResource, + InterestTierScheduleResourceWithRawResponse, + AsyncInterestTierScheduleResourceWithRawResponse, + InterestTierScheduleResourceWithStreamingResponse, + AsyncInterestTierScheduleResourceWithStreamingResponse, +) +from .loan_tape_configuration import ( + LoanTapeConfigurationResource, + AsyncLoanTapeConfigurationResource, + LoanTapeConfigurationResourceWithRawResponse, + AsyncLoanTapeConfigurationResourceWithRawResponse, + LoanTapeConfigurationResourceWithStreamingResponse, + AsyncLoanTapeConfigurationResourceWithStreamingResponse, +) from ...types.financial_account import FinancialAccount __all__ = ["FinancialAccounts", "AsyncFinancialAccounts"] @@ -88,6 +104,14 @@ def statements(self) -> Statements: def loan_tapes(self) -> LoanTapes: return LoanTapes(self._client) + @cached_property + def loan_tape_configuration(self) -> LoanTapeConfigurationResource: + return LoanTapeConfigurationResource(self._client) + + @cached_property + def interest_tier_schedule(self) -> InterestTierScheduleResource: + return InterestTierScheduleResource(self._client) + @cached_property def with_raw_response(self) -> FinancialAccountsWithRawResponse: """ @@ -396,6 +420,14 @@ def statements(self) -> AsyncStatements: def loan_tapes(self) -> AsyncLoanTapes: return AsyncLoanTapes(self._client) + @cached_property + def loan_tape_configuration(self) -> AsyncLoanTapeConfigurationResource: + return AsyncLoanTapeConfigurationResource(self._client) + + @cached_property + def interest_tier_schedule(self) -> AsyncInterestTierScheduleResource: + return AsyncInterestTierScheduleResource(self._client) + @cached_property def with_raw_response(self) -> AsyncFinancialAccountsWithRawResponse: """ @@ -728,6 +760,14 @@ def statements(self) -> StatementsWithRawResponse: def loan_tapes(self) -> LoanTapesWithRawResponse: return LoanTapesWithRawResponse(self._financial_accounts.loan_tapes) + @cached_property + def loan_tape_configuration(self) -> LoanTapeConfigurationResourceWithRawResponse: + return LoanTapeConfigurationResourceWithRawResponse(self._financial_accounts.loan_tape_configuration) + + @cached_property + def interest_tier_schedule(self) -> InterestTierScheduleResourceWithRawResponse: + return InterestTierScheduleResourceWithRawResponse(self._financial_accounts.interest_tier_schedule) + class AsyncFinancialAccountsWithRawResponse: def __init__(self, financial_accounts: AsyncFinancialAccounts) -> None: @@ -772,6 +812,14 @@ def statements(self) -> AsyncStatementsWithRawResponse: def loan_tapes(self) -> AsyncLoanTapesWithRawResponse: return AsyncLoanTapesWithRawResponse(self._financial_accounts.loan_tapes) + @cached_property + def loan_tape_configuration(self) -> AsyncLoanTapeConfigurationResourceWithRawResponse: + return AsyncLoanTapeConfigurationResourceWithRawResponse(self._financial_accounts.loan_tape_configuration) + + @cached_property + def interest_tier_schedule(self) -> AsyncInterestTierScheduleResourceWithRawResponse: + return AsyncInterestTierScheduleResourceWithRawResponse(self._financial_accounts.interest_tier_schedule) + class FinancialAccountsWithStreamingResponse: def __init__(self, financial_accounts: FinancialAccounts) -> None: @@ -816,6 +864,14 @@ def statements(self) -> StatementsWithStreamingResponse: def loan_tapes(self) -> LoanTapesWithStreamingResponse: return LoanTapesWithStreamingResponse(self._financial_accounts.loan_tapes) + @cached_property + def loan_tape_configuration(self) -> LoanTapeConfigurationResourceWithStreamingResponse: + return LoanTapeConfigurationResourceWithStreamingResponse(self._financial_accounts.loan_tape_configuration) + + @cached_property + def interest_tier_schedule(self) -> InterestTierScheduleResourceWithStreamingResponse: + return InterestTierScheduleResourceWithStreamingResponse(self._financial_accounts.interest_tier_schedule) + class AsyncFinancialAccountsWithStreamingResponse: def __init__(self, financial_accounts: AsyncFinancialAccounts) -> None: @@ -859,3 +915,11 @@ def statements(self) -> AsyncStatementsWithStreamingResponse: @cached_property def loan_tapes(self) -> AsyncLoanTapesWithStreamingResponse: return AsyncLoanTapesWithStreamingResponse(self._financial_accounts.loan_tapes) + + @cached_property + def loan_tape_configuration(self) -> AsyncLoanTapeConfigurationResourceWithStreamingResponse: + return AsyncLoanTapeConfigurationResourceWithStreamingResponse(self._financial_accounts.loan_tape_configuration) + + @cached_property + def interest_tier_schedule(self) -> AsyncInterestTierScheduleResourceWithStreamingResponse: + return AsyncInterestTierScheduleResourceWithStreamingResponse(self._financial_accounts.interest_tier_schedule) diff --git a/src/lithic/resources/financial_accounts/interest_tier_schedule.py b/src/lithic/resources/financial_accounts/interest_tier_schedule.py new file mode 100644 index 00000000..55f13682 --- /dev/null +++ b/src/lithic/resources/financial_accounts/interest_tier_schedule.py @@ -0,0 +1,681 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date + +import httpx + +from ... import _legacy_response +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...pagination import SyncSinglePage, AsyncSinglePage +from ..._base_client import AsyncPaginator, make_request_options +from ...types.financial_accounts import ( + interest_tier_schedule_list_params, + interest_tier_schedule_create_params, + interest_tier_schedule_update_params, +) +from ...types.financial_accounts.interest_tier_schedule import InterestTierSchedule + +__all__ = ["InterestTierScheduleResource", "AsyncInterestTierScheduleResource"] + + +class InterestTierScheduleResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> InterestTierScheduleResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return InterestTierScheduleResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> InterestTierScheduleResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return InterestTierScheduleResourceWithStreamingResponse(self) + + def create( + self, + financial_account_token: str, + *, + credit_product_token: str, + effective_date: Union[str, date], + tier_name: str | Omit = omit, + tier_rates: object | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> InterestTierSchedule: + """ + Create a new interest tier schedule entry for a supported financial account + + Args: + credit_product_token: Globally unique identifier for a credit product + + effective_date: Date the tier should be effective in YYYY-MM-DD format + + tier_name: Name of a tier contained in the credit product. Mutually exclusive with + tier_rates + + tier_rates: Custom rates per category. Mutually exclusive with tier_name + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return self._post( + f"/v1/financial_accounts/{financial_account_token}/interest_tier_schedule", + body=maybe_transform( + { + "credit_product_token": credit_product_token, + "effective_date": effective_date, + "tier_name": tier_name, + "tier_rates": tier_rates, + }, + interest_tier_schedule_create_params.InterestTierScheduleCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=InterestTierSchedule, + ) + + def retrieve( + self, + effective_date: Union[str, date], + *, + financial_account_token: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> InterestTierSchedule: + """ + Get a specific interest tier schedule by effective date + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + if not effective_date: + raise ValueError(f"Expected a non-empty value for `effective_date` but received {effective_date!r}") + return self._get( + f"/v1/financial_accounts/{financial_account_token}/interest_tier_schedule/{effective_date}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=InterestTierSchedule, + ) + + def update( + self, + effective_date: Union[str, date], + *, + financial_account_token: str, + tier_name: str | Omit = omit, + tier_rates: object | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> InterestTierSchedule: + """ + Update an existing interest tier schedule + + Args: + tier_name: Name of a tier contained in the credit product. Mutually exclusive with + tier_rates + + tier_rates: Custom rates per category. Mutually exclusive with tier_name + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + if not effective_date: + raise ValueError(f"Expected a non-empty value for `effective_date` but received {effective_date!r}") + return self._put( + f"/v1/financial_accounts/{financial_account_token}/interest_tier_schedule/{effective_date}", + body=maybe_transform( + { + "tier_name": tier_name, + "tier_rates": tier_rates, + }, + interest_tier_schedule_update_params.InterestTierScheduleUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=InterestTierSchedule, + ) + + def list( + self, + financial_account_token: str, + *, + after_date: Union[str, date] | Omit = omit, + before_date: Union[str, date] | Omit = omit, + for_date: Union[str, date] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncSinglePage[InterestTierSchedule]: + """ + List interest tier schedules for a financial account with optional date + filtering. + + If no date parameters are provided, returns all tier schedules. If date + parameters are provided, uses filtering to return matching schedules (max 100). + + - for_date: Returns exact match (takes precedence over other dates) + - before_date: Returns schedules with effective_date <= before_date + - after_date: Returns schedules with effective_date >= after_date + - Both before_date and after_date: Returns schedules in range + + Args: + after_date: Return schedules with effective_date >= after_date (ISO format YYYY-MM-DD) + + before_date: Return schedules with effective_date <= before_date (ISO format YYYY-MM-DD) + + for_date: Return schedule with effective_date == for_date (ISO format YYYY-MM-DD) + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return self._get_api_list( + f"/v1/financial_accounts/{financial_account_token}/interest_tier_schedule", + page=SyncSinglePage[InterestTierSchedule], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after_date": after_date, + "before_date": before_date, + "for_date": for_date, + }, + interest_tier_schedule_list_params.InterestTierScheduleListParams, + ), + ), + model=InterestTierSchedule, + ) + + def delete( + self, + effective_date: Union[str, date], + *, + financial_account_token: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete an interest tier schedule entry. + + Returns: + + - 400 Bad Request: Invalid effective_date format OR attempting to delete the + earliest tier schedule entry for a non-PENDING account + - 404 Not Found: Tier schedule entry not found for the given effective_date OR + ledger account not found + + Note: PENDING accounts can delete the earliest tier schedule entry (account + hasn't opened yet). Active/non-PENDING accounts cannot delete the earliest entry + to prevent orphaning the account. + + If the deleted tier schedule has a past effective_date and the account is + ACTIVE, the loan tape rebuild configuration will be updated to trigger rebuilds + from that date. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + if not effective_date: + raise ValueError(f"Expected a non-empty value for `effective_date` but received {effective_date!r}") + return self._delete( + f"/v1/financial_accounts/{financial_account_token}/interest_tier_schedule/{effective_date}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class AsyncInterestTierScheduleResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncInterestTierScheduleResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncInterestTierScheduleResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncInterestTierScheduleResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncInterestTierScheduleResourceWithStreamingResponse(self) + + async def create( + self, + financial_account_token: str, + *, + credit_product_token: str, + effective_date: Union[str, date], + tier_name: str | Omit = omit, + tier_rates: object | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> InterestTierSchedule: + """ + Create a new interest tier schedule entry for a supported financial account + + Args: + credit_product_token: Globally unique identifier for a credit product + + effective_date: Date the tier should be effective in YYYY-MM-DD format + + tier_name: Name of a tier contained in the credit product. Mutually exclusive with + tier_rates + + tier_rates: Custom rates per category. Mutually exclusive with tier_name + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return await self._post( + f"/v1/financial_accounts/{financial_account_token}/interest_tier_schedule", + body=await async_maybe_transform( + { + "credit_product_token": credit_product_token, + "effective_date": effective_date, + "tier_name": tier_name, + "tier_rates": tier_rates, + }, + interest_tier_schedule_create_params.InterestTierScheduleCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=InterestTierSchedule, + ) + + async def retrieve( + self, + effective_date: Union[str, date], + *, + financial_account_token: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> InterestTierSchedule: + """ + Get a specific interest tier schedule by effective date + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + if not effective_date: + raise ValueError(f"Expected a non-empty value for `effective_date` but received {effective_date!r}") + return await self._get( + f"/v1/financial_accounts/{financial_account_token}/interest_tier_schedule/{effective_date}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=InterestTierSchedule, + ) + + async def update( + self, + effective_date: Union[str, date], + *, + financial_account_token: str, + tier_name: str | Omit = omit, + tier_rates: object | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> InterestTierSchedule: + """ + Update an existing interest tier schedule + + Args: + tier_name: Name of a tier contained in the credit product. Mutually exclusive with + tier_rates + + tier_rates: Custom rates per category. Mutually exclusive with tier_name + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + if not effective_date: + raise ValueError(f"Expected a non-empty value for `effective_date` but received {effective_date!r}") + return await self._put( + f"/v1/financial_accounts/{financial_account_token}/interest_tier_schedule/{effective_date}", + body=await async_maybe_transform( + { + "tier_name": tier_name, + "tier_rates": tier_rates, + }, + interest_tier_schedule_update_params.InterestTierScheduleUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=InterestTierSchedule, + ) + + def list( + self, + financial_account_token: str, + *, + after_date: Union[str, date] | Omit = omit, + before_date: Union[str, date] | Omit = omit, + for_date: Union[str, date] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[InterestTierSchedule, AsyncSinglePage[InterestTierSchedule]]: + """ + List interest tier schedules for a financial account with optional date + filtering. + + If no date parameters are provided, returns all tier schedules. If date + parameters are provided, uses filtering to return matching schedules (max 100). + + - for_date: Returns exact match (takes precedence over other dates) + - before_date: Returns schedules with effective_date <= before_date + - after_date: Returns schedules with effective_date >= after_date + - Both before_date and after_date: Returns schedules in range + + Args: + after_date: Return schedules with effective_date >= after_date (ISO format YYYY-MM-DD) + + before_date: Return schedules with effective_date <= before_date (ISO format YYYY-MM-DD) + + for_date: Return schedule with effective_date == for_date (ISO format YYYY-MM-DD) + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return self._get_api_list( + f"/v1/financial_accounts/{financial_account_token}/interest_tier_schedule", + page=AsyncSinglePage[InterestTierSchedule], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after_date": after_date, + "before_date": before_date, + "for_date": for_date, + }, + interest_tier_schedule_list_params.InterestTierScheduleListParams, + ), + ), + model=InterestTierSchedule, + ) + + async def delete( + self, + effective_date: Union[str, date], + *, + financial_account_token: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete an interest tier schedule entry. + + Returns: + + - 400 Bad Request: Invalid effective_date format OR attempting to delete the + earliest tier schedule entry for a non-PENDING account + - 404 Not Found: Tier schedule entry not found for the given effective_date OR + ledger account not found + + Note: PENDING accounts can delete the earliest tier schedule entry (account + hasn't opened yet). Active/non-PENDING accounts cannot delete the earliest entry + to prevent orphaning the account. + + If the deleted tier schedule has a past effective_date and the account is + ACTIVE, the loan tape rebuild configuration will be updated to trigger rebuilds + from that date. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + if not effective_date: + raise ValueError(f"Expected a non-empty value for `effective_date` but received {effective_date!r}") + return await self._delete( + f"/v1/financial_accounts/{financial_account_token}/interest_tier_schedule/{effective_date}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class InterestTierScheduleResourceWithRawResponse: + def __init__(self, interest_tier_schedule: InterestTierScheduleResource) -> None: + self._interest_tier_schedule = interest_tier_schedule + + self.create = _legacy_response.to_raw_response_wrapper( + interest_tier_schedule.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + interest_tier_schedule.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + interest_tier_schedule.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + interest_tier_schedule.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + interest_tier_schedule.delete, + ) + + +class AsyncInterestTierScheduleResourceWithRawResponse: + def __init__(self, interest_tier_schedule: AsyncInterestTierScheduleResource) -> None: + self._interest_tier_schedule = interest_tier_schedule + + self.create = _legacy_response.async_to_raw_response_wrapper( + interest_tier_schedule.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + interest_tier_schedule.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + interest_tier_schedule.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + interest_tier_schedule.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + interest_tier_schedule.delete, + ) + + +class InterestTierScheduleResourceWithStreamingResponse: + def __init__(self, interest_tier_schedule: InterestTierScheduleResource) -> None: + self._interest_tier_schedule = interest_tier_schedule + + self.create = to_streamed_response_wrapper( + interest_tier_schedule.create, + ) + self.retrieve = to_streamed_response_wrapper( + interest_tier_schedule.retrieve, + ) + self.update = to_streamed_response_wrapper( + interest_tier_schedule.update, + ) + self.list = to_streamed_response_wrapper( + interest_tier_schedule.list, + ) + self.delete = to_streamed_response_wrapper( + interest_tier_schedule.delete, + ) + + +class AsyncInterestTierScheduleResourceWithStreamingResponse: + def __init__(self, interest_tier_schedule: AsyncInterestTierScheduleResource) -> None: + self._interest_tier_schedule = interest_tier_schedule + + self.create = async_to_streamed_response_wrapper( + interest_tier_schedule.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + interest_tier_schedule.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + interest_tier_schedule.update, + ) + self.list = async_to_streamed_response_wrapper( + interest_tier_schedule.list, + ) + self.delete = async_to_streamed_response_wrapper( + interest_tier_schedule.delete, + ) diff --git a/src/lithic/resources/financial_accounts/loan_tape_configuration.py b/src/lithic/resources/financial_accounts/loan_tape_configuration.py new file mode 100644 index 00000000..b07b5006 --- /dev/null +++ b/src/lithic/resources/financial_accounts/loan_tape_configuration.py @@ -0,0 +1,167 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ... import _legacy_response +from ..._types import Body, Query, Headers, NotGiven, not_given +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..._base_client import make_request_options +from ...types.financial_accounts.loan_tape_configuration import LoanTapeConfiguration + +__all__ = ["LoanTapeConfigurationResource", "AsyncLoanTapeConfigurationResource"] + + +class LoanTapeConfigurationResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> LoanTapeConfigurationResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return LoanTapeConfigurationResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> LoanTapeConfigurationResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return LoanTapeConfigurationResourceWithStreamingResponse(self) + + def retrieve( + self, + financial_account_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> LoanTapeConfiguration: + """ + Get the loan tape configuration for a given financial account. + + Args: + financial_account_token: Globally unique identifier for financial account. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return self._get( + f"/v1/financial_accounts/{financial_account_token}/loan_tape_configuration", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LoanTapeConfiguration, + ) + + +class AsyncLoanTapeConfigurationResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncLoanTapeConfigurationResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncLoanTapeConfigurationResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncLoanTapeConfigurationResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncLoanTapeConfigurationResourceWithStreamingResponse(self) + + async def retrieve( + self, + financial_account_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> LoanTapeConfiguration: + """ + Get the loan tape configuration for a given financial account. + + Args: + financial_account_token: Globally unique identifier for financial account. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return await self._get( + f"/v1/financial_accounts/{financial_account_token}/loan_tape_configuration", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LoanTapeConfiguration, + ) + + +class LoanTapeConfigurationResourceWithRawResponse: + def __init__(self, loan_tape_configuration: LoanTapeConfigurationResource) -> None: + self._loan_tape_configuration = loan_tape_configuration + + self.retrieve = _legacy_response.to_raw_response_wrapper( + loan_tape_configuration.retrieve, + ) + + +class AsyncLoanTapeConfigurationResourceWithRawResponse: + def __init__(self, loan_tape_configuration: AsyncLoanTapeConfigurationResource) -> None: + self._loan_tape_configuration = loan_tape_configuration + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + loan_tape_configuration.retrieve, + ) + + +class LoanTapeConfigurationResourceWithStreamingResponse: + def __init__(self, loan_tape_configuration: LoanTapeConfigurationResource) -> None: + self._loan_tape_configuration = loan_tape_configuration + + self.retrieve = to_streamed_response_wrapper( + loan_tape_configuration.retrieve, + ) + + +class AsyncLoanTapeConfigurationResourceWithStreamingResponse: + def __init__(self, loan_tape_configuration: AsyncLoanTapeConfigurationResource) -> None: + self._loan_tape_configuration = loan_tape_configuration + + self.retrieve = async_to_streamed_response_wrapper( + loan_tape_configuration.retrieve, + ) diff --git a/src/lithic/types/financial_accounts/__init__.py b/src/lithic/types/financial_accounts/__init__.py index 0a094585..1938293c 100644 --- a/src/lithic/types/financial_accounts/__init__.py +++ b/src/lithic/types/financial_accounts/__init__.py @@ -9,6 +9,12 @@ from .balance_list_params import BalanceListParams as BalanceListParams from .loan_tape_list_params import LoanTapeListParams as LoanTapeListParams from .statement_list_params import StatementListParams as StatementListParams +from .interest_tier_schedule import InterestTierSchedule as InterestTierSchedule +from .loan_tape_configuration import LoanTapeConfiguration as LoanTapeConfiguration from .financial_account_credit_config import FinancialAccountCreditConfig as FinancialAccountCreditConfig +from .loan_tape_rebuild_configuration import LoanTapeRebuildConfiguration as LoanTapeRebuildConfiguration from .financial_transaction_list_params import FinancialTransactionListParams as FinancialTransactionListParams from .credit_configuration_update_params import CreditConfigurationUpdateParams as CreditConfigurationUpdateParams +from .interest_tier_schedule_list_params import InterestTierScheduleListParams as InterestTierScheduleListParams +from .interest_tier_schedule_create_params import InterestTierScheduleCreateParams as InterestTierScheduleCreateParams +from .interest_tier_schedule_update_params import InterestTierScheduleUpdateParams as InterestTierScheduleUpdateParams diff --git a/src/lithic/types/financial_accounts/interest_tier_schedule.py b/src/lithic/types/financial_accounts/interest_tier_schedule.py new file mode 100644 index 00000000..de815562 --- /dev/null +++ b/src/lithic/types/financial_accounts/interest_tier_schedule.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import date + +from ..._models import BaseModel + +__all__ = ["InterestTierSchedule"] + + +class InterestTierSchedule(BaseModel): + """Entry in the Tier Schedule of an account""" + + credit_product_token: str + """Globally unique identifier for a credit product""" + + effective_date: date + """Date the tier should be effective in YYYY-MM-DD format""" + + tier_name: Optional[str] = None + """Name of a tier contained in the credit product. + + Mutually exclusive with tier_rates + """ + + tier_rates: Optional[object] = None + """Custom rates per category. Mutually exclusive with tier_name""" diff --git a/src/lithic/types/financial_accounts/interest_tier_schedule_create_params.py b/src/lithic/types/financial_accounts/interest_tier_schedule_create_params.py new file mode 100644 index 00000000..dc7c11e9 --- /dev/null +++ b/src/lithic/types/financial_accounts/interest_tier_schedule_create_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["InterestTierScheduleCreateParams"] + + +class InterestTierScheduleCreateParams(TypedDict, total=False): + credit_product_token: Required[str] + """Globally unique identifier for a credit product""" + + effective_date: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + """Date the tier should be effective in YYYY-MM-DD format""" + + tier_name: str + """Name of a tier contained in the credit product. + + Mutually exclusive with tier_rates + """ + + tier_rates: object + """Custom rates per category. Mutually exclusive with tier_name""" diff --git a/src/lithic/types/financial_accounts/interest_tier_schedule_list_params.py b/src/lithic/types/financial_accounts/interest_tier_schedule_list_params.py new file mode 100644 index 00000000..094f9ac1 --- /dev/null +++ b/src/lithic/types/financial_accounts/interest_tier_schedule_list_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["InterestTierScheduleListParams"] + + +class InterestTierScheduleListParams(TypedDict, total=False): + after_date: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """Return schedules with effective_date >= after_date (ISO format YYYY-MM-DD)""" + + before_date: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """Return schedules with effective_date <= before_date (ISO format YYYY-MM-DD)""" + + for_date: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """Return schedule with effective_date == for_date (ISO format YYYY-MM-DD)""" diff --git a/src/lithic/types/financial_accounts/interest_tier_schedule_update_params.py b/src/lithic/types/financial_accounts/interest_tier_schedule_update_params.py new file mode 100644 index 00000000..dd1cd9c0 --- /dev/null +++ b/src/lithic/types/financial_accounts/interest_tier_schedule_update_params.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["InterestTierScheduleUpdateParams"] + + +class InterestTierScheduleUpdateParams(TypedDict, total=False): + financial_account_token: Required[str] + + tier_name: str + """Name of a tier contained in the credit product. + + Mutually exclusive with tier_rates + """ + + tier_rates: object + """Custom rates per category. Mutually exclusive with tier_name""" diff --git a/src/lithic/types/financial_accounts/loan_tape_configuration.py b/src/lithic/types/financial_accounts/loan_tape_configuration.py new file mode 100644 index 00000000..02554353 --- /dev/null +++ b/src/lithic/types/financial_accounts/loan_tape_configuration.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from ..._models import BaseModel +from .loan_tape_rebuild_configuration import LoanTapeRebuildConfiguration + +__all__ = ["LoanTapeConfiguration"] + + +class LoanTapeConfiguration(BaseModel): + """Configuration for loan tapes""" + + created_at: datetime + + financial_account_token: str + + instance_token: str + + updated_at: datetime + + credit_product_token: Optional[str] = None + + loan_tape_rebuild_configuration: Optional[LoanTapeRebuildConfiguration] = None + """Configuration for building loan tapes""" + + tier_schedule_changed_at: Optional[datetime] = None diff --git a/src/lithic/types/financial_accounts/loan_tape_rebuild_configuration.py b/src/lithic/types/financial_accounts/loan_tape_rebuild_configuration.py new file mode 100644 index 00000000..81fadc2c --- /dev/null +++ b/src/lithic/types/financial_accounts/loan_tape_rebuild_configuration.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import date + +from ..._models import BaseModel + +__all__ = ["LoanTapeRebuildConfiguration"] + + +class LoanTapeRebuildConfiguration(BaseModel): + """Configuration for building loan tapes""" + + rebuild_needed: bool + """Whether the account's loan tapes need to be rebuilt or not""" + + last_rebuild: Optional[date] = None + """The date for which the account's loan tapes were last rebuilt""" + + rebuild_from: Optional[date] = None + """Date from which to start rebuilding from if the account requires a rebuild""" diff --git a/tests/api_resources/financial_accounts/test_interest_tier_schedule.py b/tests/api_resources/financial_accounts/test_interest_tier_schedule.py new file mode 100644 index 00000000..fcac228d --- /dev/null +++ b/tests/api_resources/financial_accounts/test_interest_tier_schedule.py @@ -0,0 +1,566 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic._utils import parse_date +from lithic.pagination import SyncSinglePage, AsyncSinglePage +from lithic.types.financial_accounts import ( + InterestTierSchedule, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestInterestTierSchedule: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Lithic) -> None: + interest_tier_schedule = client.financial_accounts.interest_tier_schedule.create( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + ) + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Lithic) -> None: + interest_tier_schedule = client.financial_accounts.interest_tier_schedule.create( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + tier_name="tier_name", + tier_rates={}, + ) + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Lithic) -> None: + response = client.financial_accounts.interest_tier_schedule.with_raw_response.create( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + interest_tier_schedule = response.parse() + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Lithic) -> None: + with client.financial_accounts.interest_tier_schedule.with_streaming_response.create( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + interest_tier_schedule = response.parse() + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + client.financial_accounts.interest_tier_schedule.with_raw_response.create( + financial_account_token="", + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + ) + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + interest_tier_schedule = client.financial_accounts.interest_tier_schedule.retrieve( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.financial_accounts.interest_tier_schedule.with_raw_response.retrieve( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + interest_tier_schedule = response.parse() + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.financial_accounts.interest_tier_schedule.with_streaming_response.retrieve( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + interest_tier_schedule = response.parse() + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + client.financial_accounts.interest_tier_schedule.with_raw_response.retrieve( + effective_date=parse_date("2019-12-27"), + financial_account_token="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `effective_date` but received ''"): + client.financial_accounts.interest_tier_schedule.with_raw_response.retrieve( + effective_date="", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + def test_method_update(self, client: Lithic) -> None: + interest_tier_schedule = client.financial_accounts.interest_tier_schedule.update( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Lithic) -> None: + interest_tier_schedule = client.financial_accounts.interest_tier_schedule.update( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + tier_name="tier_name", + tier_rates={}, + ) + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Lithic) -> None: + response = client.financial_accounts.interest_tier_schedule.with_raw_response.update( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + interest_tier_schedule = response.parse() + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Lithic) -> None: + with client.financial_accounts.interest_tier_schedule.with_streaming_response.update( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + interest_tier_schedule = response.parse() + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + client.financial_accounts.interest_tier_schedule.with_raw_response.update( + effective_date=parse_date("2019-12-27"), + financial_account_token="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `effective_date` but received ''"): + client.financial_accounts.interest_tier_schedule.with_raw_response.update( + effective_date="", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + def test_method_list(self, client: Lithic) -> None: + interest_tier_schedule = client.financial_accounts.interest_tier_schedule.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(SyncSinglePage[InterestTierSchedule], interest_tier_schedule, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Lithic) -> None: + interest_tier_schedule = client.financial_accounts.interest_tier_schedule.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + after_date=parse_date("2019-12-27"), + before_date=parse_date("2019-12-27"), + for_date=parse_date("2019-12-27"), + ) + assert_matches_type(SyncSinglePage[InterestTierSchedule], interest_tier_schedule, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Lithic) -> None: + response = client.financial_accounts.interest_tier_schedule.with_raw_response.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + interest_tier_schedule = response.parse() + assert_matches_type(SyncSinglePage[InterestTierSchedule], interest_tier_schedule, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Lithic) -> None: + with client.financial_accounts.interest_tier_schedule.with_streaming_response.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + interest_tier_schedule = response.parse() + assert_matches_type(SyncSinglePage[InterestTierSchedule], interest_tier_schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + client.financial_accounts.interest_tier_schedule.with_raw_response.list( + financial_account_token="", + ) + + @parametrize + def test_method_delete(self, client: Lithic) -> None: + interest_tier_schedule = client.financial_accounts.interest_tier_schedule.delete( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert interest_tier_schedule is None + + @parametrize + def test_raw_response_delete(self, client: Lithic) -> None: + response = client.financial_accounts.interest_tier_schedule.with_raw_response.delete( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + interest_tier_schedule = response.parse() + assert interest_tier_schedule is None + + @parametrize + def test_streaming_response_delete(self, client: Lithic) -> None: + with client.financial_accounts.interest_tier_schedule.with_streaming_response.delete( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + interest_tier_schedule = response.parse() + assert interest_tier_schedule is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + client.financial_accounts.interest_tier_schedule.with_raw_response.delete( + effective_date=parse_date("2019-12-27"), + financial_account_token="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `effective_date` but received ''"): + client.financial_accounts.interest_tier_schedule.with_raw_response.delete( + effective_date="", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + +class TestAsyncInterestTierSchedule: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncLithic) -> None: + interest_tier_schedule = await async_client.financial_accounts.interest_tier_schedule.create( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + ) + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> None: + interest_tier_schedule = await async_client.financial_accounts.interest_tier_schedule.create( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + tier_name="tier_name", + tier_rates={}, + ) + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncLithic) -> None: + response = await async_client.financial_accounts.interest_tier_schedule.with_raw_response.create( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + interest_tier_schedule = response.parse() + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncLithic) -> None: + async with async_client.financial_accounts.interest_tier_schedule.with_streaming_response.create( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + interest_tier_schedule = await response.parse() + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + await async_client.financial_accounts.interest_tier_schedule.with_raw_response.create( + financial_account_token="", + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + interest_tier_schedule = await async_client.financial_accounts.interest_tier_schedule.retrieve( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.financial_accounts.interest_tier_schedule.with_raw_response.retrieve( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + interest_tier_schedule = response.parse() + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.financial_accounts.interest_tier_schedule.with_streaming_response.retrieve( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + interest_tier_schedule = await response.parse() + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + await async_client.financial_accounts.interest_tier_schedule.with_raw_response.retrieve( + effective_date=parse_date("2019-12-27"), + financial_account_token="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `effective_date` but received ''"): + await async_client.financial_accounts.interest_tier_schedule.with_raw_response.retrieve( + effective_date="", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncLithic) -> None: + interest_tier_schedule = await async_client.financial_accounts.interest_tier_schedule.update( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncLithic) -> None: + interest_tier_schedule = await async_client.financial_accounts.interest_tier_schedule.update( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + tier_name="tier_name", + tier_rates={}, + ) + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncLithic) -> None: + response = await async_client.financial_accounts.interest_tier_schedule.with_raw_response.update( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + interest_tier_schedule = response.parse() + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncLithic) -> None: + async with async_client.financial_accounts.interest_tier_schedule.with_streaming_response.update( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + interest_tier_schedule = await response.parse() + assert_matches_type(InterestTierSchedule, interest_tier_schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + await async_client.financial_accounts.interest_tier_schedule.with_raw_response.update( + effective_date=parse_date("2019-12-27"), + financial_account_token="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `effective_date` but received ''"): + await async_client.financial_accounts.interest_tier_schedule.with_raw_response.update( + effective_date="", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncLithic) -> None: + interest_tier_schedule = await async_client.financial_accounts.interest_tier_schedule.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AsyncSinglePage[InterestTierSchedule], interest_tier_schedule, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: + interest_tier_schedule = await async_client.financial_accounts.interest_tier_schedule.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + after_date=parse_date("2019-12-27"), + before_date=parse_date("2019-12-27"), + for_date=parse_date("2019-12-27"), + ) + assert_matches_type(AsyncSinglePage[InterestTierSchedule], interest_tier_schedule, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncLithic) -> None: + response = await async_client.financial_accounts.interest_tier_schedule.with_raw_response.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + interest_tier_schedule = response.parse() + assert_matches_type(AsyncSinglePage[InterestTierSchedule], interest_tier_schedule, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: + async with async_client.financial_accounts.interest_tier_schedule.with_streaming_response.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + interest_tier_schedule = await response.parse() + assert_matches_type(AsyncSinglePage[InterestTierSchedule], interest_tier_schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + await async_client.financial_accounts.interest_tier_schedule.with_raw_response.list( + financial_account_token="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncLithic) -> None: + interest_tier_schedule = await async_client.financial_accounts.interest_tier_schedule.delete( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert interest_tier_schedule is None + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncLithic) -> None: + response = await async_client.financial_accounts.interest_tier_schedule.with_raw_response.delete( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + interest_tier_schedule = response.parse() + assert interest_tier_schedule is None + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncLithic) -> None: + async with async_client.financial_accounts.interest_tier_schedule.with_streaming_response.delete( + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + interest_tier_schedule = await response.parse() + assert interest_tier_schedule is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + await async_client.financial_accounts.interest_tier_schedule.with_raw_response.delete( + effective_date=parse_date("2019-12-27"), + financial_account_token="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `effective_date` but received ''"): + await async_client.financial_accounts.interest_tier_schedule.with_raw_response.delete( + effective_date="", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) diff --git a/tests/api_resources/financial_accounts/test_loan_tape_configuration.py b/tests/api_resources/financial_accounts/test_loan_tape_configuration.py new file mode 100644 index 00000000..e906241a --- /dev/null +++ b/tests/api_resources/financial_accounts/test_loan_tape_configuration.py @@ -0,0 +1,104 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types.financial_accounts import LoanTapeConfiguration + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestLoanTapeConfiguration: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + loan_tape_configuration = client.financial_accounts.loan_tape_configuration.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(LoanTapeConfiguration, loan_tape_configuration, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.financial_accounts.loan_tape_configuration.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + loan_tape_configuration = response.parse() + assert_matches_type(LoanTapeConfiguration, loan_tape_configuration, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.financial_accounts.loan_tape_configuration.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + loan_tape_configuration = response.parse() + assert_matches_type(LoanTapeConfiguration, loan_tape_configuration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + client.financial_accounts.loan_tape_configuration.with_raw_response.retrieve( + "", + ) + + +class TestAsyncLoanTapeConfiguration: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + loan_tape_configuration = await async_client.financial_accounts.loan_tape_configuration.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(LoanTapeConfiguration, loan_tape_configuration, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.financial_accounts.loan_tape_configuration.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + loan_tape_configuration = response.parse() + assert_matches_type(LoanTapeConfiguration, loan_tape_configuration, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.financial_accounts.loan_tape_configuration.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + loan_tape_configuration = await response.parse() + assert_matches_type(LoanTapeConfiguration, loan_tape_configuration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + await async_client.financial_accounts.loan_tape_configuration.with_raw_response.retrieve( + "", + ) From e12df381dda14514d7bc8ce5f7fe7ea882d0fb0f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 20:30:14 +0000 Subject: [PATCH 07/12] chore(internal): make `test_proxy_environment_variables` more resilient --- tests/test_client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index 62647a32..dddc69d8 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -982,6 +982,8 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has this set + monkeypatch.delenv("HTTP_PROXY", raising=False) client = DefaultHttpxClient() @@ -1923,6 +1925,8 @@ async def test_get_platform(self) -> None: async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has this set + monkeypatch.delenv("HTTP_PROXY", raising=False) client = DefaultAsyncHttpxClient() From 9d0e87814e2369c791ee5b9f8a4431c9de38e505 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:45:31 +0000 Subject: [PATCH 08/12] chore(internal): make `test_proxy_environment_variables` more resilient to env --- tests/test_client.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index dddc69d8..9cbf4688 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -982,8 +982,14 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") - # Delete in case our environment has this set + # Delete in case our environment has any proxy env vars set monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultHttpxClient() @@ -1925,8 +1931,14 @@ async def test_get_platform(self) -> None: async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") - # Delete in case our environment has this set + # Delete in case our environment has any proxy env vars set monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultAsyncHttpxClient() From 710c501c3695a51743437488769f1132578c23dd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:28:26 +0000 Subject: [PATCH 09/12] feat(api): Add account holder entity endpoints --- .stats.yml | 8 +- api.md | 31 +- src/lithic/_client.py | 2 +- .../resources/account_holders/__init__.py | 33 ++ .../{ => account_holders}/account_holders.py | 68 +++- .../resources/account_holders/entities.py | 352 ++++++++++++++++++ src/lithic/types/account_holders/__init__.py | 7 + .../account_holders/account_holder_entity.py | 76 ++++ .../account_holders/entity_create_params.py | 81 ++++ .../account_holders/entity_create_response.py | 61 +++ .../api_resources/account_holders/__init__.py | 1 + .../account_holders/test_entities.py | 352 ++++++++++++++++++ 12 files changed, 1040 insertions(+), 32 deletions(-) create mode 100644 src/lithic/resources/account_holders/__init__.py rename src/lithic/resources/{ => account_holders}/account_holders.py (98%) create mode 100644 src/lithic/resources/account_holders/entities.py create mode 100644 src/lithic/types/account_holders/__init__.py create mode 100644 src/lithic/types/account_holders/account_holder_entity.py create mode 100644 src/lithic/types/account_holders/entity_create_params.py create mode 100644 src/lithic/types/account_holders/entity_create_response.py create mode 100644 tests/api_resources/account_holders/__init__.py create mode 100644 tests/api_resources/account_holders/test_entities.py diff --git a/.stats.yml b/.stats.yml index 258b775f..a75b80c8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 182 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-f99894d5b6eda608756c9e5e9868c81c4ce8c74c4d8958370cc3799766a13d65.yml -openapi_spec_hash: 2f364e16b58e5a9759fc9f772cb33f3c -config_hash: 2ee394874b7eb4cbe06f044b7376a6ba +configured_endpoints: 184 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-c1dbf87770ddffb9d6d1ada33d1c43739d43498ff687c1d58e403ffc08f6f863.yml +openapi_spec_hash: f488e595de02180bb904da283b7a1c39 +config_hash: 925e84bc73b1b9b5eb0ffd230fc9800f diff --git a/api.md b/api.md index 18672599..2c5b988d 100644 --- a/api.md +++ b/api.md @@ -63,15 +63,28 @@ from lithic.types import ( Methods: -- client.account_holders.create(\*\*params) -> AccountHolderCreateResponse -- client.account_holders.retrieve(account_holder_token) -> AccountHolder -- client.account_holders.update(account_holder_token, \*\*params) -> AccountHolderUpdateResponse -- client.account_holders.list(\*\*params) -> SyncSinglePage[AccountHolder] -- client.account_holders.list_documents(account_holder_token) -> AccountHolderListDocumentsResponse -- client.account_holders.retrieve_document(document_token, \*, account_holder_token) -> Document -- client.account_holders.simulate_enrollment_document_review(\*\*params) -> Document -- client.account_holders.simulate_enrollment_review(\*\*params) -> AccountHolderSimulateEnrollmentReviewResponse -- client.account_holders.upload_document(account_holder_token, \*\*params) -> Document +- client.account_holders.create(\*\*params) -> AccountHolderCreateResponse +- client.account_holders.retrieve(account_holder_token) -> AccountHolder +- client.account_holders.update(account_holder_token, \*\*params) -> AccountHolderUpdateResponse +- client.account_holders.list(\*\*params) -> SyncSinglePage[AccountHolder] +- client.account_holders.list_documents(account_holder_token) -> AccountHolderListDocumentsResponse +- client.account_holders.retrieve_document(document_token, \*, account_holder_token) -> Document +- client.account_holders.simulate_enrollment_document_review(\*\*params) -> Document +- client.account_holders.simulate_enrollment_review(\*\*params) -> AccountHolderSimulateEnrollmentReviewResponse +- client.account_holders.upload_document(account_holder_token, \*\*params) -> Document + +## Entities + +Types: + +```python +from lithic.types.account_holders import AccountHolderEntity, EntityCreateResponse +``` + +Methods: + +- client.account_holders.entities.create(account_holder_token, \*\*params) -> EntityCreateResponse +- client.account_holders.entities.delete(entity_token, \*, account_holder_token) -> AccountHolderEntity # AuthRules diff --git a/src/lithic/_client.py b/src/lithic/_client.py index 3dd30033..4f9fd51a 100644 --- a/src/lithic/_client.py +++ b/src/lithic/_client.py @@ -82,7 +82,6 @@ from .resources.tokenizations import Tokenizations, AsyncTokenizations from .resources.book_transfers import BookTransfers, AsyncBookTransfers from .resources.funding_events import FundingEvents, AsyncFundingEvents - from .resources.account_holders import AccountHolders, AsyncAccountHolders from .resources.reports.reports import Reports, AsyncReports from .resources.transfer_limits import TransferLimits, AsyncTransferLimits from .resources.account_activity import AccountActivity, AsyncAccountActivity @@ -97,6 +96,7 @@ from .resources.auth_stream_enrollment import AuthStreamEnrollment, AsyncAuthStreamEnrollment from .resources.tokenization_decisioning import TokenizationDecisioning, AsyncTokenizationDecisioning from .resources.transactions.transactions import Transactions, AsyncTransactions + from .resources.account_holders.account_holders import AccountHolders, AsyncAccountHolders from .resources.credit_products.credit_products import CreditProducts, AsyncCreditProducts from .resources.financial_accounts.financial_accounts import FinancialAccounts, AsyncFinancialAccounts from .resources.external_bank_accounts.external_bank_accounts import ExternalBankAccounts, AsyncExternalBankAccounts diff --git a/src/lithic/resources/account_holders/__init__.py b/src/lithic/resources/account_holders/__init__.py new file mode 100644 index 00000000..583bc2e8 --- /dev/null +++ b/src/lithic/resources/account_holders/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .entities import ( + Entities, + AsyncEntities, + EntitiesWithRawResponse, + AsyncEntitiesWithRawResponse, + EntitiesWithStreamingResponse, + AsyncEntitiesWithStreamingResponse, +) +from .account_holders import ( + AccountHolders, + AsyncAccountHolders, + AccountHoldersWithRawResponse, + AsyncAccountHoldersWithRawResponse, + AccountHoldersWithStreamingResponse, + AsyncAccountHoldersWithStreamingResponse, +) + +__all__ = [ + "Entities", + "AsyncEntities", + "EntitiesWithRawResponse", + "AsyncEntitiesWithRawResponse", + "EntitiesWithStreamingResponse", + "AsyncEntitiesWithStreamingResponse", + "AccountHolders", + "AsyncAccountHolders", + "AccountHoldersWithRawResponse", + "AsyncAccountHoldersWithRawResponse", + "AccountHoldersWithStreamingResponse", + "AsyncAccountHoldersWithStreamingResponse", +] diff --git a/src/lithic/resources/account_holders.py b/src/lithic/resources/account_holders/account_holders.py similarity index 98% rename from src/lithic/resources/account_holders.py rename to src/lithic/resources/account_holders/account_holders.py index b4440f72..ee283367 100644 --- a/src/lithic/resources/account_holders.py +++ b/src/lithic/resources/account_holders/account_holders.py @@ -8,8 +8,8 @@ import httpx -from .. import _legacy_response -from ..types import ( +from ... import _legacy_response +from ...types import ( account_holder_list_params, account_holder_create_params, account_holder_update_params, @@ -17,27 +17,39 @@ account_holder_simulate_enrollment_review_params, account_holder_simulate_enrollment_document_review_params, ) -from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given -from .._utils import is_given, required_args, maybe_transform, async_maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from .._constants import DEFAULT_TIMEOUT -from ..pagination import SyncSinglePage, AsyncSinglePage -from .._base_client import AsyncPaginator, make_request_options -from ..types.account_holder import AccountHolder -from ..types.shared.document import Document -from ..types.address_update_param import AddressUpdateParam -from ..types.shared_params.address import Address -from ..types.account_holder_create_response import AccountHolderCreateResponse -from ..types.account_holder_update_response import AccountHolderUpdateResponse -from ..types.account_holder_list_documents_response import AccountHolderListDocumentsResponse -from ..types.account_holder_simulate_enrollment_review_response import AccountHolderSimulateEnrollmentReviewResponse +from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ..._utils import is_given, required_args, maybe_transform, async_maybe_transform +from .entities import ( + Entities, + AsyncEntities, + EntitiesWithRawResponse, + AsyncEntitiesWithRawResponse, + EntitiesWithStreamingResponse, + AsyncEntitiesWithStreamingResponse, +) +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..._constants import DEFAULT_TIMEOUT +from ...pagination import SyncSinglePage, AsyncSinglePage +from ..._base_client import AsyncPaginator, make_request_options +from ...types.account_holder import AccountHolder +from ...types.shared.document import Document +from ...types.address_update_param import AddressUpdateParam +from ...types.shared_params.address import Address +from ...types.account_holder_create_response import AccountHolderCreateResponse +from ...types.account_holder_update_response import AccountHolderUpdateResponse +from ...types.account_holder_list_documents_response import AccountHolderListDocumentsResponse +from ...types.account_holder_simulate_enrollment_review_response import AccountHolderSimulateEnrollmentReviewResponse __all__ = ["AccountHolders", "AsyncAccountHolders"] class AccountHolders(SyncAPIResource): + @cached_property + def entities(self) -> Entities: + return Entities(self._client) + @cached_property def with_raw_response(self) -> AccountHoldersWithRawResponse: """ @@ -1124,6 +1136,10 @@ def upload_document( class AsyncAccountHolders(AsyncAPIResource): + @cached_property + def entities(self) -> AsyncEntities: + return AsyncEntities(self._client) + @cached_property def with_raw_response(self) -> AsyncAccountHoldersWithRawResponse: """ @@ -2241,6 +2257,10 @@ def __init__(self, account_holders: AccountHolders) -> None: account_holders.upload_document, ) + @cached_property + def entities(self) -> EntitiesWithRawResponse: + return EntitiesWithRawResponse(self._account_holders.entities) + class AsyncAccountHoldersWithRawResponse: def __init__(self, account_holders: AsyncAccountHolders) -> None: @@ -2274,6 +2294,10 @@ def __init__(self, account_holders: AsyncAccountHolders) -> None: account_holders.upload_document, ) + @cached_property + def entities(self) -> AsyncEntitiesWithRawResponse: + return AsyncEntitiesWithRawResponse(self._account_holders.entities) + class AccountHoldersWithStreamingResponse: def __init__(self, account_holders: AccountHolders) -> None: @@ -2307,6 +2331,10 @@ def __init__(self, account_holders: AccountHolders) -> None: account_holders.upload_document, ) + @cached_property + def entities(self) -> EntitiesWithStreamingResponse: + return EntitiesWithStreamingResponse(self._account_holders.entities) + class AsyncAccountHoldersWithStreamingResponse: def __init__(self, account_holders: AsyncAccountHolders) -> None: @@ -2339,3 +2367,7 @@ def __init__(self, account_holders: AsyncAccountHolders) -> None: self.upload_document = async_to_streamed_response_wrapper( account_holders.upload_document, ) + + @cached_property + def entities(self) -> AsyncEntitiesWithStreamingResponse: + return AsyncEntitiesWithStreamingResponse(self._account_holders.entities) diff --git a/src/lithic/resources/account_holders/entities.py b/src/lithic/resources/account_holders/entities.py new file mode 100644 index 00000000..ebc307b6 --- /dev/null +++ b/src/lithic/resources/account_holders/entities.py @@ -0,0 +1,352 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ... import _legacy_response +from ..._types import Body, Query, Headers, NotGiven, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..._base_client import make_request_options +from ...types.account_holders import entity_create_params +from ...types.account_holders.account_holder_entity import AccountHolderEntity +from ...types.account_holders.entity_create_response import EntityCreateResponse + +__all__ = ["Entities", "AsyncEntities"] + + +class Entities(SyncAPIResource): + @cached_property + def with_raw_response(self) -> EntitiesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return EntitiesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> EntitiesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return EntitiesWithStreamingResponse(self) + + def create( + self, + account_holder_token: str, + *, + address: entity_create_params.Address, + dob: str, + email: str, + first_name: str, + government_id: str, + last_name: str, + phone_number: str, + type: Literal["BENEFICIAL_OWNER_INDIVIDUAL", "CONTROL_PERSON"], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EntityCreateResponse: + """ + Create a new beneficial owner or replace the control person entity on an + existing KYB account holder. This endpoint is only applicable for account + holders enrolled through a KYB workflow with the Persona KYB provider. A new + control person can only replace the existing one. A maximum of 4 beneficial + owners can be associated with an account holder. + + Args: + address: Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + + dob: Individual's date of birth, as an RFC 3339 date. + + email: Individual's email address. If utilizing Lithic for chargeback processing, this + customer email address may be used to communicate dispute status and resolution. + + first_name: Individual's first name, as it appears on government-issued identity documents. + + government_id: Government-issued identification number (required for identity verification and + compliance with banking regulations). Social Security Numbers (SSN) and + Individual Taxpayer Identification Numbers (ITIN) are currently supported, + entered as full nine-digits, with or without hyphens + + last_name: Individual's last name, as it appears on government-issued identity documents. + + phone_number: Individual's phone number, entered in E.164 format. + + type: The type of entity to create on the account holder + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not account_holder_token: + raise ValueError( + f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" + ) + return self._post( + f"/v1/account_holders/{account_holder_token}/entities", + body=maybe_transform( + { + "address": address, + "dob": dob, + "email": email, + "first_name": first_name, + "government_id": government_id, + "last_name": last_name, + "phone_number": phone_number, + "type": type, + }, + entity_create_params.EntityCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EntityCreateResponse, + ) + + def delete( + self, + entity_token: str, + *, + account_holder_token: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AccountHolderEntity: + """Deactivate a beneficial owner entity on an existing KYB account holder. + + Only + beneficial owner entities can be deactivated. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not account_holder_token: + raise ValueError( + f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" + ) + if not entity_token: + raise ValueError(f"Expected a non-empty value for `entity_token` but received {entity_token!r}") + return self._delete( + f"/v1/account_holders/{account_holder_token}/entities/{entity_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AccountHolderEntity, + ) + + +class AsyncEntities(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncEntitiesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncEntitiesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncEntitiesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncEntitiesWithStreamingResponse(self) + + async def create( + self, + account_holder_token: str, + *, + address: entity_create_params.Address, + dob: str, + email: str, + first_name: str, + government_id: str, + last_name: str, + phone_number: str, + type: Literal["BENEFICIAL_OWNER_INDIVIDUAL", "CONTROL_PERSON"], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EntityCreateResponse: + """ + Create a new beneficial owner or replace the control person entity on an + existing KYB account holder. This endpoint is only applicable for account + holders enrolled through a KYB workflow with the Persona KYB provider. A new + control person can only replace the existing one. A maximum of 4 beneficial + owners can be associated with an account holder. + + Args: + address: Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + + dob: Individual's date of birth, as an RFC 3339 date. + + email: Individual's email address. If utilizing Lithic for chargeback processing, this + customer email address may be used to communicate dispute status and resolution. + + first_name: Individual's first name, as it appears on government-issued identity documents. + + government_id: Government-issued identification number (required for identity verification and + compliance with banking regulations). Social Security Numbers (SSN) and + Individual Taxpayer Identification Numbers (ITIN) are currently supported, + entered as full nine-digits, with or without hyphens + + last_name: Individual's last name, as it appears on government-issued identity documents. + + phone_number: Individual's phone number, entered in E.164 format. + + type: The type of entity to create on the account holder + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not account_holder_token: + raise ValueError( + f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" + ) + return await self._post( + f"/v1/account_holders/{account_holder_token}/entities", + body=await async_maybe_transform( + { + "address": address, + "dob": dob, + "email": email, + "first_name": first_name, + "government_id": government_id, + "last_name": last_name, + "phone_number": phone_number, + "type": type, + }, + entity_create_params.EntityCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EntityCreateResponse, + ) + + async def delete( + self, + entity_token: str, + *, + account_holder_token: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AccountHolderEntity: + """Deactivate a beneficial owner entity on an existing KYB account holder. + + Only + beneficial owner entities can be deactivated. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not account_holder_token: + raise ValueError( + f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" + ) + if not entity_token: + raise ValueError(f"Expected a non-empty value for `entity_token` but received {entity_token!r}") + return await self._delete( + f"/v1/account_holders/{account_holder_token}/entities/{entity_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AccountHolderEntity, + ) + + +class EntitiesWithRawResponse: + def __init__(self, entities: Entities) -> None: + self._entities = entities + + self.create = _legacy_response.to_raw_response_wrapper( + entities.create, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + entities.delete, + ) + + +class AsyncEntitiesWithRawResponse: + def __init__(self, entities: AsyncEntities) -> None: + self._entities = entities + + self.create = _legacy_response.async_to_raw_response_wrapper( + entities.create, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + entities.delete, + ) + + +class EntitiesWithStreamingResponse: + def __init__(self, entities: Entities) -> None: + self._entities = entities + + self.create = to_streamed_response_wrapper( + entities.create, + ) + self.delete = to_streamed_response_wrapper( + entities.delete, + ) + + +class AsyncEntitiesWithStreamingResponse: + def __init__(self, entities: AsyncEntities) -> None: + self._entities = entities + + self.create = async_to_streamed_response_wrapper( + entities.create, + ) + self.delete = async_to_streamed_response_wrapper( + entities.delete, + ) diff --git a/src/lithic/types/account_holders/__init__.py b/src/lithic/types/account_holders/__init__.py new file mode 100644 index 00000000..f7f58159 --- /dev/null +++ b/src/lithic/types/account_holders/__init__.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .entity_create_params import EntityCreateParams as EntityCreateParams +from .account_holder_entity import AccountHolderEntity as AccountHolderEntity +from .entity_create_response import EntityCreateResponse as EntityCreateResponse diff --git a/src/lithic/types/account_holders/account_holder_entity.py b/src/lithic/types/account_holders/account_holder_entity.py new file mode 100644 index 00000000..16512cc1 --- /dev/null +++ b/src/lithic/types/account_holders/account_holder_entity.py @@ -0,0 +1,76 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["AccountHolderEntity", "Address"] + + +class Address(BaseModel): + """Individual's current address""" + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class AccountHolderEntity(BaseModel): + """Information about an entity associated with an account holder""" + + token: str + """Globally unique identifier for the entity""" + + account_holder_token: str + """Globally unique identifier for the account holder""" + + address: Address + """Individual's current address""" + + dob: Optional[str] = None + """Individual's date of birth, as an RFC 3339 date""" + + email: Optional[str] = None + """Individual's email address""" + + first_name: Optional[str] = None + """Individual's first name, as it appears on government-issued identity documents""" + + last_name: Optional[str] = None + """Individual's last name, as it appears on government-issued identity documents""" + + phone_number: Optional[str] = None + """Individual's phone number, entered in E.164 format""" + + status: Literal["ACCEPTED", "INACTIVE", "PENDING_REVIEW", "REJECTED"] + """The status of the entity""" + + type: Literal["BENEFICIAL_OWNER_INDIVIDUAL", "CONTROL_PERSON"] + """The type of entity""" diff --git a/src/lithic/types/account_holders/entity_create_params.py b/src/lithic/types/account_holders/entity_create_params.py new file mode 100644 index 00000000..a97fc419 --- /dev/null +++ b/src/lithic/types/account_holders/entity_create_params.py @@ -0,0 +1,81 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["EntityCreateParams", "Address"] + + +class EntityCreateParams(TypedDict, total=False): + address: Required[Address] + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Required[str] + """Individual's date of birth, as an RFC 3339 date.""" + + email: Required[str] + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: Required[str] + """Individual's first name, as it appears on government-issued identity documents.""" + + government_id: Required[str] + """ + Government-issued identification number (required for identity verification and + compliance with banking regulations). Social Security Numbers (SSN) and + Individual Taxpayer Identification Numbers (ITIN) are currently supported, + entered as full nine-digits, with or without hyphens + """ + + last_name: Required[str] + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: Required[str] + """Individual's phone number, entered in E.164 format.""" + + type: Required[Literal["BENEFICIAL_OWNER_INDIVIDUAL", "CONTROL_PERSON"]] + """The type of entity to create on the account holder""" + + +class Address(TypedDict, total=False): + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + address1: Required[str] + """Valid deliverable address (no PO boxes).""" + + city: Required[str] + """Name of city.""" + + country: Required[str] + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: Required[str] + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: Required[str] + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: str + """Unit or apartment number (if applicable).""" diff --git a/src/lithic/types/account_holders/entity_create_response.py b/src/lithic/types/account_holders/entity_create_response.py new file mode 100644 index 00000000..c316e842 --- /dev/null +++ b/src/lithic/types/account_holders/entity_create_response.py @@ -0,0 +1,61 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel +from ..required_document import RequiredDocument + +__all__ = ["EntityCreateResponse"] + + +class EntityCreateResponse(BaseModel): + """ + Response body for creating a new beneficial owner or replacing the control person entity on an existing KYB account holder. + """ + + token: str + """Globally unique identifier for the entity""" + + account_holder_token: str + """Globally unique identifier for the account holder""" + + created: datetime + """Timestamp of when the entity was created""" + + required_documents: List[RequiredDocument] + """A list of documents required for the entity to be approved""" + + status: Literal["ACCEPTED", "INACTIVE", "PENDING_REVIEW", "REJECTED"] + """Entity verification status""" + + status_reasons: List[ + Literal[ + "ADDRESS_VERIFICATION_FAILURE", + "AGE_THRESHOLD_FAILURE", + "COMPLETE_VERIFICATION_FAILURE", + "DOB_VERIFICATION_FAILURE", + "ID_VERIFICATION_FAILURE", + "MAX_DOCUMENT_ATTEMPTS", + "MAX_RESUBMISSION_ATTEMPTS", + "NAME_VERIFICATION_FAILURE", + "OTHER_VERIFICATION_FAILURE", + "RISK_THRESHOLD_FAILURE", + "WATCHLIST_ALERT_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ID_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ADDRESS_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_NAME_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_BUSINESS_OFFICERS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_SOS_FILING_INACTIVE", + "PRIMARY_BUSINESS_ENTITY_SOS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_CMRA_FAILURE", + "PRIMARY_BUSINESS_ENTITY_WATCHLIST_FAILURE", + "PRIMARY_BUSINESS_ENTITY_REGISTERED_AGENT_FAILURE", + "CONTROL_PERSON_BLOCKLIST_ALERT_FAILURE", + "CONTROL_PERSON_ID_VERIFICATION_FAILURE", + "CONTROL_PERSON_DOB_VERIFICATION_FAILURE", + "CONTROL_PERSON_NAME_VERIFICATION_FAILURE", + ] + ] + """Reason for the evaluation status""" diff --git a/tests/api_resources/account_holders/__init__.py b/tests/api_resources/account_holders/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/account_holders/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/account_holders/test_entities.py b/tests/api_resources/account_holders/test_entities.py new file mode 100644 index 00000000..7013ad53 --- /dev/null +++ b/tests/api_resources/account_holders/test_entities.py @@ -0,0 +1,352 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types.account_holders import AccountHolderEntity, EntityCreateResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestEntities: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Lithic) -> None: + entity = client.account_holders.entities.create( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + address={ + "address1": "300 Normal Forest Way", + "city": "Portland", + "country": "USA", + "postal_code": "90210", + "state": "OR", + }, + dob="1991-03-08T08:00:00Z", + email="tim@left-earth.com", + first_name="Timmy", + government_id="211-23-1412", + last_name="Turner", + phone_number="+15555555555", + type="BENEFICIAL_OWNER_INDIVIDUAL", + ) + assert_matches_type(EntityCreateResponse, entity, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Lithic) -> None: + entity = client.account_holders.entities.create( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + address={ + "address1": "300 Normal Forest Way", + "city": "Portland", + "country": "USA", + "postal_code": "90210", + "state": "OR", + "address2": "address2", + }, + dob="1991-03-08T08:00:00Z", + email="tim@left-earth.com", + first_name="Timmy", + government_id="211-23-1412", + last_name="Turner", + phone_number="+15555555555", + type="BENEFICIAL_OWNER_INDIVIDUAL", + ) + assert_matches_type(EntityCreateResponse, entity, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Lithic) -> None: + response = client.account_holders.entities.with_raw_response.create( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + address={ + "address1": "300 Normal Forest Way", + "city": "Portland", + "country": "USA", + "postal_code": "90210", + "state": "OR", + }, + dob="1991-03-08T08:00:00Z", + email="tim@left-earth.com", + first_name="Timmy", + government_id="211-23-1412", + last_name="Turner", + phone_number="+15555555555", + type="BENEFICIAL_OWNER_INDIVIDUAL", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entity = response.parse() + assert_matches_type(EntityCreateResponse, entity, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Lithic) -> None: + with client.account_holders.entities.with_streaming_response.create( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + address={ + "address1": "300 Normal Forest Way", + "city": "Portland", + "country": "USA", + "postal_code": "90210", + "state": "OR", + }, + dob="1991-03-08T08:00:00Z", + email="tim@left-earth.com", + first_name="Timmy", + government_id="211-23-1412", + last_name="Turner", + phone_number="+15555555555", + type="BENEFICIAL_OWNER_INDIVIDUAL", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entity = response.parse() + assert_matches_type(EntityCreateResponse, entity, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): + client.account_holders.entities.with_raw_response.create( + account_holder_token="", + address={ + "address1": "300 Normal Forest Way", + "city": "Portland", + "country": "USA", + "postal_code": "90210", + "state": "OR", + }, + dob="1991-03-08T08:00:00Z", + email="tim@left-earth.com", + first_name="Timmy", + government_id="211-23-1412", + last_name="Turner", + phone_number="+15555555555", + type="BENEFICIAL_OWNER_INDIVIDUAL", + ) + + @parametrize + def test_method_delete(self, client: Lithic) -> None: + entity = client.account_holders.entities.delete( + entity_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AccountHolderEntity, entity, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Lithic) -> None: + response = client.account_holders.entities.with_raw_response.delete( + entity_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entity = response.parse() + assert_matches_type(AccountHolderEntity, entity, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Lithic) -> None: + with client.account_holders.entities.with_streaming_response.delete( + entity_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entity = response.parse() + assert_matches_type(AccountHolderEntity, entity, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): + client.account_holders.entities.with_raw_response.delete( + entity_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entity_token` but received ''"): + client.account_holders.entities.with_raw_response.delete( + entity_token="", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + +class TestAsyncEntities: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncLithic) -> None: + entity = await async_client.account_holders.entities.create( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + address={ + "address1": "300 Normal Forest Way", + "city": "Portland", + "country": "USA", + "postal_code": "90210", + "state": "OR", + }, + dob="1991-03-08T08:00:00Z", + email="tim@left-earth.com", + first_name="Timmy", + government_id="211-23-1412", + last_name="Turner", + phone_number="+15555555555", + type="BENEFICIAL_OWNER_INDIVIDUAL", + ) + assert_matches_type(EntityCreateResponse, entity, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> None: + entity = await async_client.account_holders.entities.create( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + address={ + "address1": "300 Normal Forest Way", + "city": "Portland", + "country": "USA", + "postal_code": "90210", + "state": "OR", + "address2": "address2", + }, + dob="1991-03-08T08:00:00Z", + email="tim@left-earth.com", + first_name="Timmy", + government_id="211-23-1412", + last_name="Turner", + phone_number="+15555555555", + type="BENEFICIAL_OWNER_INDIVIDUAL", + ) + assert_matches_type(EntityCreateResponse, entity, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncLithic) -> None: + response = await async_client.account_holders.entities.with_raw_response.create( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + address={ + "address1": "300 Normal Forest Way", + "city": "Portland", + "country": "USA", + "postal_code": "90210", + "state": "OR", + }, + dob="1991-03-08T08:00:00Z", + email="tim@left-earth.com", + first_name="Timmy", + government_id="211-23-1412", + last_name="Turner", + phone_number="+15555555555", + type="BENEFICIAL_OWNER_INDIVIDUAL", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entity = response.parse() + assert_matches_type(EntityCreateResponse, entity, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncLithic) -> None: + async with async_client.account_holders.entities.with_streaming_response.create( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + address={ + "address1": "300 Normal Forest Way", + "city": "Portland", + "country": "USA", + "postal_code": "90210", + "state": "OR", + }, + dob="1991-03-08T08:00:00Z", + email="tim@left-earth.com", + first_name="Timmy", + government_id="211-23-1412", + last_name="Turner", + phone_number="+15555555555", + type="BENEFICIAL_OWNER_INDIVIDUAL", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entity = await response.parse() + assert_matches_type(EntityCreateResponse, entity, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): + await async_client.account_holders.entities.with_raw_response.create( + account_holder_token="", + address={ + "address1": "300 Normal Forest Way", + "city": "Portland", + "country": "USA", + "postal_code": "90210", + "state": "OR", + }, + dob="1991-03-08T08:00:00Z", + email="tim@left-earth.com", + first_name="Timmy", + government_id="211-23-1412", + last_name="Turner", + phone_number="+15555555555", + type="BENEFICIAL_OWNER_INDIVIDUAL", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncLithic) -> None: + entity = await async_client.account_holders.entities.delete( + entity_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AccountHolderEntity, entity, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncLithic) -> None: + response = await async_client.account_holders.entities.with_raw_response.delete( + entity_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entity = response.parse() + assert_matches_type(AccountHolderEntity, entity, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncLithic) -> None: + async with async_client.account_holders.entities.with_streaming_response.delete( + entity_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entity = await response.parse() + assert_matches_type(AccountHolderEntity, entity, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): + await async_client.account_holders.entities.with_raw_response.delete( + entity_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entity_token` but received ''"): + await async_client.account_holders.entities.with_raw_response.delete( + entity_token="", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) From f202486f3cb2c60338be8d38f09eb23e82acf1ae Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 13:22:31 +0000 Subject: [PATCH 10/12] docs: Remove CONDITIONAL_BLOCK from docs --- .stats.yml | 4 ++-- src/lithic/resources/auth_rules/v2/v2.py | 18 ++++++++++++------ src/lithic/types/auth_rules/auth_rule.py | 3 ++- .../auth_rules/conditional_block_parameters.py | 2 ++ .../conditional_block_parameters_param.py | 2 ++ .../types/auth_rules/v2_create_params.py | 9 ++++++--- 6 files changed, 26 insertions(+), 12 deletions(-) diff --git a/.stats.yml b/.stats.yml index a75b80c8..e8643c16 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 184 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-c1dbf87770ddffb9d6d1ada33d1c43739d43498ff687c1d58e403ffc08f6f863.yml -openapi_spec_hash: f488e595de02180bb904da283b7a1c39 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-179734893d610c68eb1c49d41be91edc2ad243885e4972fd901835eec233b122.yml +openapi_spec_hash: c60105fa2dd66c0d5b15d17c556db7c1 config_hash: 925e84bc73b1b9b5eb0ffd230fc9800f diff --git a/src/lithic/resources/auth_rules/v2/v2.py b/src/lithic/resources/auth_rules/v2/v2.py index 6a212e97..5e207bb1 100644 --- a/src/lithic/resources/auth_rules/v2/v2.py +++ b/src/lithic/resources/auth_rules/v2/v2.py @@ -95,7 +95,8 @@ def create( several event streams, the effective one is defined by the separate `event_stream` field. - - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_BLOCK`: Deprecated. Use `CONDITIONAL_ACTION` instead. + AUTHORIZATION event stream. - `VELOCITY_LIMIT`: AUTHORIZATION event stream. - `MERCHANT_LOCK`: AUTHORIZATION event stream. - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, @@ -148,7 +149,8 @@ def create( several event streams, the effective one is defined by the separate `event_stream` field. - - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_BLOCK`: Deprecated. Use `CONDITIONAL_ACTION` instead. + AUTHORIZATION event stream. - `VELOCITY_LIMIT`: AUTHORIZATION event stream. - `MERCHANT_LOCK`: AUTHORIZATION event stream. - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, @@ -198,7 +200,8 @@ def create( several event streams, the effective one is defined by the separate `event_stream` field. - - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_BLOCK`: Deprecated. Use `CONDITIONAL_ACTION` instead. + AUTHORIZATION event stream. - `VELOCITY_LIMIT`: AUTHORIZATION event stream. - `MERCHANT_LOCK`: AUTHORIZATION event stream. - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, @@ -900,7 +903,8 @@ async def create( several event streams, the effective one is defined by the separate `event_stream` field. - - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_BLOCK`: Deprecated. Use `CONDITIONAL_ACTION` instead. + AUTHORIZATION event stream. - `VELOCITY_LIMIT`: AUTHORIZATION event stream. - `MERCHANT_LOCK`: AUTHORIZATION event stream. - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, @@ -953,7 +957,8 @@ async def create( several event streams, the effective one is defined by the separate `event_stream` field. - - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_BLOCK`: Deprecated. Use `CONDITIONAL_ACTION` instead. + AUTHORIZATION event stream. - `VELOCITY_LIMIT`: AUTHORIZATION event stream. - `MERCHANT_LOCK`: AUTHORIZATION event stream. - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, @@ -1003,7 +1008,8 @@ async def create( several event streams, the effective one is defined by the separate `event_stream` field. - - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_BLOCK`: Deprecated. Use `CONDITIONAL_ACTION` instead. + AUTHORIZATION event stream. - `VELOCITY_LIMIT`: AUTHORIZATION event stream. - `MERCHANT_LOCK`: AUTHORIZATION event stream. - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, diff --git a/src/lithic/types/auth_rules/auth_rule.py b/src/lithic/types/auth_rules/auth_rule.py index 09f2f172..3708f7e1 100644 --- a/src/lithic/types/auth_rules/auth_rule.py +++ b/src/lithic/types/auth_rules/auth_rule.py @@ -101,7 +101,8 @@ class AuthRule(BaseModel): evaluated. For rules that can be applied to one of several event streams, the effective one is defined by the separate `event_stream` field. - - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_BLOCK`: Deprecated. Use `CONDITIONAL_ACTION` instead. + AUTHORIZATION event stream. - `VELOCITY_LIMIT`: AUTHORIZATION event stream. - `MERCHANT_LOCK`: AUTHORIZATION event stream. - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, diff --git a/src/lithic/types/auth_rules/conditional_block_parameters.py b/src/lithic/types/auth_rules/conditional_block_parameters.py index b615fbe2..48f698bf 100644 --- a/src/lithic/types/auth_rules/conditional_block_parameters.py +++ b/src/lithic/types/auth_rules/conditional_block_parameters.py @@ -9,4 +9,6 @@ class ConditionalBlockParameters(BaseModel): + """Deprecated: Use CONDITIONAL_ACTION instead.""" + conditions: List[AuthRuleCondition] diff --git a/src/lithic/types/auth_rules/conditional_block_parameters_param.py b/src/lithic/types/auth_rules/conditional_block_parameters_param.py index b9a456a3..539073c8 100644 --- a/src/lithic/types/auth_rules/conditional_block_parameters_param.py +++ b/src/lithic/types/auth_rules/conditional_block_parameters_param.py @@ -11,4 +11,6 @@ class ConditionalBlockParametersParam(TypedDict, total=False): + """Deprecated: Use CONDITIONAL_ACTION instead.""" + conditions: Required[Iterable[AuthRuleConditionParam]] diff --git a/src/lithic/types/auth_rules/v2_create_params.py b/src/lithic/types/auth_rules/v2_create_params.py index e9f9d2d3..2af4efd9 100644 --- a/src/lithic/types/auth_rules/v2_create_params.py +++ b/src/lithic/types/auth_rules/v2_create_params.py @@ -37,7 +37,8 @@ class AccountLevelRule(TypedDict, total=False): evaluated. For rules that can be applied to one of several event streams, the effective one is defined by the separate `event_stream` field. - - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_BLOCK`: Deprecated. Use `CONDITIONAL_ACTION` instead. + AUTHORIZATION event stream. - `VELOCITY_LIMIT`: AUTHORIZATION event stream. - `MERCHANT_LOCK`: AUTHORIZATION event stream. - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, @@ -82,7 +83,8 @@ class CardLevelRule(TypedDict, total=False): evaluated. For rules that can be applied to one of several event streams, the effective one is defined by the separate `event_stream` field. - - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_BLOCK`: Deprecated. Use `CONDITIONAL_ACTION` instead. + AUTHORIZATION event stream. - `VELOCITY_LIMIT`: AUTHORIZATION event stream. - `MERCHANT_LOCK`: AUTHORIZATION event stream. - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, @@ -121,7 +123,8 @@ class ProgramLevelRule(TypedDict, total=False): evaluated. For rules that can be applied to one of several event streams, the effective one is defined by the separate `event_stream` field. - - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_BLOCK`: Deprecated. Use `CONDITIONAL_ACTION` instead. + AUTHORIZATION event stream. - `VELOCITY_LIMIT`: AUTHORIZATION event stream. - `MERCHANT_LOCK`: AUTHORIZATION event stream. - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, From 36532d836f7cd3ef23b6c70194cd50f9495644ec Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:20:27 +0000 Subject: [PATCH 11/12] fix(api): Correct token_metadata field name in tokenization.approval_request schema --- .stats.yml | 6 +++--- api.md | 2 +- src/lithic/types/__init__.py | 2 +- ..._wallet_tokenization_approval_request_webhook_event.py | 8 ++++---- ...digital_wallet_token_metadata.py => token_metadata.py} | 5 +++-- .../types/tokenization_approval_request_webhook_event.py | 8 ++++---- .../tokenization_decisioning_request_webhook_event.py | 8 ++++---- 7 files changed, 20 insertions(+), 19 deletions(-) rename src/lithic/types/{digital_wallet_token_metadata.py => token_metadata.py} (93%) diff --git a/.stats.yml b/.stats.yml index e8643c16..31a62cc4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 184 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-179734893d610c68eb1c49d41be91edc2ad243885e4972fd901835eec233b122.yml -openapi_spec_hash: c60105fa2dd66c0d5b15d17c556db7c1 -config_hash: 925e84bc73b1b9b5eb0ffd230fc9800f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-a45946df228eec554b3cd2491f658bd5a45cb91509da0a9f92d50468ea88072f.yml +openapi_spec_hash: 24c7c13e1e7385cab5442ca66091ffc6 +config_hash: 50031f78031362c2e4900222b9ce7ada diff --git a/api.md b/api.md index 2c5b988d..81c83721 100644 --- a/api.md +++ b/api.md @@ -174,7 +174,7 @@ Types: ```python from lithic.types import ( Device, - DigitalWalletTokenMetadata, + TokenMetadata, Tokenization, TokenizationDeclineReason, TokenizationRuleResult, diff --git a/src/lithic/types/__init__.py b/src/lithic/types/__init__.py index 93932f6a..5532206f 100644 --- a/src/lithic/types/__init__.py +++ b/src/lithic/types/__init__.py @@ -33,6 +33,7 @@ from .funding_event import FundingEvent as FundingEvent from .network_total import NetworkTotal as NetworkTotal from .account_holder import AccountHolder as AccountHolder +from .token_metadata import TokenMetadata as TokenMetadata from .card_bulk_order import CardBulkOrder as CardBulkOrder from .message_attempt import MessageAttempt as MessageAttempt from .network_program import NetworkProgram as NetworkProgram @@ -131,7 +132,6 @@ from .balance_updated_webhook_event import BalanceUpdatedWebhookEvent as BalanceUpdatedWebhookEvent from .card_bulk_order_create_params import CardBulkOrderCreateParams as CardBulkOrderCreateParams from .card_bulk_order_update_params import CardBulkOrderUpdateParams as CardBulkOrderUpdateParams -from .digital_wallet_token_metadata import DigitalWalletTokenMetadata as DigitalWalletTokenMetadata from .dispute_list_evidences_params import DisputeListEvidencesParams as DisputeListEvidencesParams from .dispute_updated_webhook_event import DisputeUpdatedWebhookEvent as DisputeUpdatedWebhookEvent from .external_bank_account_address import ExternalBankAccountAddress as ExternalBankAccountAddress diff --git a/src/lithic/types/digital_wallet_tokenization_approval_request_webhook_event.py b/src/lithic/types/digital_wallet_tokenization_approval_request_webhook_event.py index ab01de75..16e99491 100644 --- a/src/lithic/types/digital_wallet_tokenization_approval_request_webhook_event.py +++ b/src/lithic/types/digital_wallet_tokenization_approval_request_webhook_event.py @@ -6,11 +6,11 @@ from .device import Device from .._models import BaseModel +from .token_metadata import TokenMetadata from .tokenization_tfa_reason import TokenizationTfaReason from .wallet_decisioning_info import WalletDecisioningInfo from .tokenization_rule_result import TokenizationRuleResult from .tokenization_decline_reason import TokenizationDeclineReason -from .digital_wallet_token_metadata import DigitalWalletTokenMetadata __all__ = ["DigitalWalletTokenizationApprovalRequestWebhookEvent", "CustomerTokenizationDecision"] @@ -46,6 +46,9 @@ class DigitalWalletTokenizationApprovalRequestWebhookEvent(BaseModel): customer_tokenization_decision: Optional[CustomerTokenizationDecision] = None """Contains the metadata for the customer tokenization decision.""" + digital_wallet_token_metadata: TokenMetadata + """Contains the metadata for the digital wallet being tokenized.""" + event_type: Literal["digital_wallet.tokenization_approval_request"] """The name of this event""" @@ -65,9 +68,6 @@ class DigitalWalletTokenizationApprovalRequestWebhookEvent(BaseModel): device: Optional[Device] = None - digital_wallet_token_metadata: Optional[DigitalWalletTokenMetadata] = None - """Contains the metadata for the digital wallet being tokenized.""" - rule_results: Optional[List[TokenizationRuleResult]] = None """Results from rules that were evaluated for this tokenization""" diff --git a/src/lithic/types/digital_wallet_token_metadata.py b/src/lithic/types/token_metadata.py similarity index 93% rename from src/lithic/types/digital_wallet_token_metadata.py rename to src/lithic/types/token_metadata.py index 2e26e00c..f8df2d11 100644 --- a/src/lithic/types/digital_wallet_token_metadata.py +++ b/src/lithic/types/token_metadata.py @@ -5,7 +5,7 @@ from .._models import BaseModel -__all__ = ["DigitalWalletTokenMetadata", "PaymentAccountInfo", "PaymentAccountInfoAccountHolderData"] +__all__ = ["TokenMetadata", "PaymentAccountInfo", "PaymentAccountInfoAccountHolderData"] class PaymentAccountInfoAccountHolderData(BaseModel): @@ -42,7 +42,7 @@ class PaymentAccountInfo(BaseModel): """ -class DigitalWalletTokenMetadata(BaseModel): +class TokenMetadata(BaseModel): """Contains the metadata for the digital wallet being tokenized.""" payment_account_info: PaymentAccountInfo @@ -68,6 +68,7 @@ class DigitalWalletTokenMetadata(BaseModel): "FACEBOOK", "FITBIT_PAY", "GARMIN_PAY", + "GOOGLE_PAY", "MICROSOFT_PAY", "NETFLIX", "SAMSUNG_PAY", diff --git a/src/lithic/types/tokenization_approval_request_webhook_event.py b/src/lithic/types/tokenization_approval_request_webhook_event.py index 9e6e6f11..f5e0df25 100644 --- a/src/lithic/types/tokenization_approval_request_webhook_event.py +++ b/src/lithic/types/tokenization_approval_request_webhook_event.py @@ -6,11 +6,11 @@ from .device import Device from .._models import BaseModel +from .token_metadata import TokenMetadata from .tokenization_tfa_reason import TokenizationTfaReason from .wallet_decisioning_info import WalletDecisioningInfo from .tokenization_rule_result import TokenizationRuleResult from .tokenization_decline_reason import TokenizationDeclineReason -from .digital_wallet_token_metadata import DigitalWalletTokenMetadata __all__ = ["TokenizationApprovalRequestWebhookEvent", "CustomerTokenizationDecision"] @@ -55,6 +55,9 @@ class TokenizationApprovalRequestWebhookEvent(BaseModel): APPROVED/VERIFICATION_REQUIRED/DENIED. """ + token_metadata: TokenMetadata + """Contains the metadata for the digital wallet being tokenized.""" + tokenization_channel: Literal["DIGITAL_WALLET", "MERCHANT"] """The channel through which the tokenization was made.""" @@ -65,9 +68,6 @@ class TokenizationApprovalRequestWebhookEvent(BaseModel): device: Optional[Device] = None - digital_wallet_token_metadata: Optional[DigitalWalletTokenMetadata] = None - """Contains the metadata for the digital wallet being tokenized.""" - rule_results: Optional[List[TokenizationRuleResult]] = None """Results from rules that were evaluated for this tokenization""" diff --git a/src/lithic/types/tokenization_decisioning_request_webhook_event.py b/src/lithic/types/tokenization_decisioning_request_webhook_event.py index 957d6266..c44df2dc 100644 --- a/src/lithic/types/tokenization_decisioning_request_webhook_event.py +++ b/src/lithic/types/tokenization_decisioning_request_webhook_event.py @@ -6,8 +6,8 @@ from .device import Device from .._models import BaseModel +from .token_metadata import TokenMetadata from .wallet_decisioning_info import WalletDecisioningInfo -from .digital_wallet_token_metadata import DigitalWalletTokenMetadata __all__ = ["TokenizationDecisioningRequestWebhookEvent"] @@ -26,6 +26,9 @@ class TokenizationDecisioningRequestWebhookEvent(BaseModel): created: datetime """Indicate when the request was received from Mastercard or Visa""" + digital_wallet_token_metadata: TokenMetadata + """Contains the metadata for the digital wallet being tokenized.""" + event_type: Literal["digital_wallet.tokenization_approval_request"] """The name of this event""" @@ -45,9 +48,6 @@ class TokenizationDecisioningRequestWebhookEvent(BaseModel): device: Optional[Device] = None - digital_wallet_token_metadata: Optional[DigitalWalletTokenMetadata] = None - """Contains the metadata for the digital wallet being tokenized.""" - tokenization_source: Optional[ Literal["ACCOUNT_ON_FILE", "CONTACTLESS_TAP", "MANUAL_PROVISION", "PUSH_PROVISION", "TOKEN", "UNKNOWN"] ] = None From e4050953aef1d8a6a070ddd40166e31ece0ebaab Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:20:53 +0000 Subject: [PATCH 12/12] release: 0.116.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/lithic/_version.py | 2 +- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d3dc9f51..988e843f 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.115.0" + ".": "0.116.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 981334cd..9bbf5ae5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +## 0.116.0 (2026-02-27) + +Full Changelog: [v0.115.0...v0.116.0](https://github.com/lithic-com/lithic-python/compare/v0.115.0...v0.116.0) + +### Features + +* **api:** Add account holder entity endpoints ([710c501](https://github.com/lithic-com/lithic-python/commit/710c501c3695a51743437488769f1132578c23dd)) +* **api:** Add INTEREST_AND_FEES_PAUSED substatus to financial account ([c4a37de](https://github.com/lithic-com/lithic-python/commit/c4a37de9f0f7821e54d05702c4d1d809124fc7bc)) +* **api:** Expose MIL interest schedules and loan tape configuration endpoints ([5c6bcd7](https://github.com/lithic-com/lithic-python/commit/5c6bcd740933255291c0fd0b482021c4a50d0e78)) + + +### Bug Fixes + +* **api:** Correct token_metadata field name in tokenization.approval_request schema ([36532d8](https://github.com/lithic-com/lithic-python/commit/36532d836f7cd3ef23b6c70194cd50f9495644ec)) + + +### Chores + +* **dependencies:** require standardwebhooks 1.0.1 ([7dae789](https://github.com/lithic-com/lithic-python/commit/7dae7892de61a765ab4bca8c7af3046d90fe2ff4)) +* **internal:** add request options to SSE classes ([1b49e55](https://github.com/lithic-com/lithic-python/commit/1b49e550b0e6fe3c5203740a9634ee6264046489)) +* **internal:** make `test_proxy_environment_variables` more resilient ([e12df38](https://github.com/lithic-com/lithic-python/commit/e12df381dda14514d7bc8ce5f7fe7ea882d0fb0f)) +* **internal:** make `test_proxy_environment_variables` more resilient to env ([9d0e878](https://github.com/lithic-com/lithic-python/commit/9d0e87814e2369c791ee5b9f8a4431c9de38e505)) +* update mock server docs ([ec268b7](https://github.com/lithic-com/lithic-python/commit/ec268b7426be15578b2eaf92ca81107cbf9084e6)) + + +### Documentation + +* Remove CONDITIONAL_BLOCK from docs ([f202486](https://github.com/lithic-com/lithic-python/commit/f202486f3cb2c60338be8d38f09eb23e82acf1ae)) + ## 0.115.0 (2026-02-13) Full Changelog: [v0.114.0...v0.115.0](https://github.com/lithic-com/lithic-python/compare/v0.114.0...v0.115.0) diff --git a/pyproject.toml b/pyproject.toml index b175a6da..18c9a87e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lithic" -version = "0.115.0" +version = "0.116.0" description = "The official Python library for the lithic API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/lithic/_version.py b/src/lithic/_version.py index 9b480c33..d7f6788a 100644 --- a/src/lithic/_version.py +++ b/src/lithic/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "lithic" -__version__ = "0.115.0" # x-release-please-version +__version__ = "0.116.0" # x-release-please-version