From 6f8fe45cb794e6283855863693c12143ec2b4871 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 18 Sep 2026 15:48:29 +0000
Subject: [PATCH 1/2] feat(api): manual updates
---
.stats.yml | 6 +-
api.md | 77 +++-
src/prelude_python_sdk/_client.py | 46 +-
src/prelude_python_sdk/resources/__init__.py | 14 +
.../resources/intel/__init__.py | 33 ++
.../resources/intel/intel.py | 120 +++++
src/prelude_python_sdk/resources/intel/kyc.py | 270 +++++++++++
src/prelude_python_sdk/resources/notify.py | 134 ++++++
.../resources/verification/__init__.py | 33 ++
.../resources/verification/phone/__init__.py | 33 ++
.../resources/verification/phone/history.py | 436 ++++++++++++++++++
.../resources/verification/phone/phone.py | 108 +++++
.../{ => verification}/verification.py | 64 ++-
.../verification_management/__init__.py | 33 ++
.../verification_management/sandbox.py | 363 +++++++++++++++
.../verification_management.py | 64 ++-
src/prelude_python_sdk/resources/watch.py | 184 +++++++-
src/prelude_python_sdk/types/__init__.py | 5 +
.../types/intel/__init__.py | 6 +
.../types/intel/kyc_match_params.py | 40 ++
.../types/intel/kyc_match_response.py | 84 ++++
.../types/notify_reply_params.py | 34 ++
.../types/notify_reply_response.py | 31 ++
.../types/shared/__init__.py | 4 +
.../types/shared/signals.py | 75 +++
src/prelude_python_sdk/types/shared/target.py | 17 +
.../types/shared_params/__init__.py | 4 +
.../types/shared_params/signals.py | 74 +++
.../types/shared_params/target.py | 17 +
.../types/verification/__init__.py | 3 +
.../types/verification/phone/__init__.py | 10 +
.../verification/phone/history_list_params.py | 85 ++++
.../phone/history_list_response.py | 113 +++++
.../phone/history_retrieve_response.py | 333 +++++++++++++
.../phone/phone_verification_carrier.py | 15 +
.../phone/phone_verification_money.py | 17 +
.../phone_verification_psd2_transaction.py | 15 +
.../types/verification_check_params.py | 19 +-
.../types/verification_create_params.py | 85 +---
.../types/verification_management/__init__.py | 8 +
.../sandbox_add_phone_number_params.py | 18 +
.../sandbox_add_phone_number_response.py | 13 +
.../sandbox_delete_phone_number_response.py | 10 +
.../sandbox_list_phone_numbers_response.py | 24 +
.../types/watch_evaluate_params.py | 48 ++
.../types/watch_evaluate_response.py | 125 +++++
.../types/watch_predict_params.py | 84 +---
.../types/watch_send_events_params.py | 16 +-
.../types/watch_send_feedbacks_params.py | 16 +-
tests/api_resources/intel/__init__.py | 1 +
tests/api_resources/intel/test_kyc.py | 133 ++++++
tests/api_resources/test_notify.py | 97 ++++
tests/api_resources/test_watch.py | 143 ++++++
tests/api_resources/verification/__init__.py | 1 +
.../verification/phone/__init__.py | 1 +
.../verification/phone/test_history.py | 190 ++++++++
.../verification_management/__init__.py | 1 +
.../verification_management/test_sandbox.py | 224 +++++++++
58 files changed, 4012 insertions(+), 245 deletions(-)
create mode 100644 src/prelude_python_sdk/resources/intel/__init__.py
create mode 100644 src/prelude_python_sdk/resources/intel/intel.py
create mode 100644 src/prelude_python_sdk/resources/intel/kyc.py
create mode 100644 src/prelude_python_sdk/resources/verification/__init__.py
create mode 100644 src/prelude_python_sdk/resources/verification/phone/__init__.py
create mode 100644 src/prelude_python_sdk/resources/verification/phone/history.py
create mode 100644 src/prelude_python_sdk/resources/verification/phone/phone.py
rename src/prelude_python_sdk/resources/{ => verification}/verification.py (87%)
create mode 100644 src/prelude_python_sdk/resources/verification_management/__init__.py
create mode 100644 src/prelude_python_sdk/resources/verification_management/sandbox.py
rename src/prelude_python_sdk/resources/{ => verification_management}/verification_management.py (90%)
create mode 100644 src/prelude_python_sdk/types/intel/__init__.py
create mode 100644 src/prelude_python_sdk/types/intel/kyc_match_params.py
create mode 100644 src/prelude_python_sdk/types/intel/kyc_match_response.py
create mode 100644 src/prelude_python_sdk/types/notify_reply_params.py
create mode 100644 src/prelude_python_sdk/types/notify_reply_response.py
create mode 100644 src/prelude_python_sdk/types/shared/__init__.py
create mode 100644 src/prelude_python_sdk/types/shared/signals.py
create mode 100644 src/prelude_python_sdk/types/shared/target.py
create mode 100644 src/prelude_python_sdk/types/shared_params/__init__.py
create mode 100644 src/prelude_python_sdk/types/shared_params/signals.py
create mode 100644 src/prelude_python_sdk/types/shared_params/target.py
create mode 100644 src/prelude_python_sdk/types/verification/__init__.py
create mode 100644 src/prelude_python_sdk/types/verification/phone/__init__.py
create mode 100644 src/prelude_python_sdk/types/verification/phone/history_list_params.py
create mode 100644 src/prelude_python_sdk/types/verification/phone/history_list_response.py
create mode 100644 src/prelude_python_sdk/types/verification/phone/history_retrieve_response.py
create mode 100644 src/prelude_python_sdk/types/verification/phone/phone_verification_carrier.py
create mode 100644 src/prelude_python_sdk/types/verification/phone/phone_verification_money.py
create mode 100644 src/prelude_python_sdk/types/verification/phone/phone_verification_psd2_transaction.py
create mode 100644 src/prelude_python_sdk/types/verification_management/__init__.py
create mode 100644 src/prelude_python_sdk/types/verification_management/sandbox_add_phone_number_params.py
create mode 100644 src/prelude_python_sdk/types/verification_management/sandbox_add_phone_number_response.py
create mode 100644 src/prelude_python_sdk/types/verification_management/sandbox_delete_phone_number_response.py
create mode 100644 src/prelude_python_sdk/types/verification_management/sandbox_list_phone_numbers_response.py
create mode 100644 src/prelude_python_sdk/types/watch_evaluate_params.py
create mode 100644 src/prelude_python_sdk/types/watch_evaluate_response.py
create mode 100644 tests/api_resources/intel/__init__.py
create mode 100644 tests/api_resources/intel/test_kyc.py
create mode 100644 tests/api_resources/verification/__init__.py
create mode 100644 tests/api_resources/verification/phone/__init__.py
create mode 100644 tests/api_resources/verification/phone/test_history.py
create mode 100644 tests/api_resources/verification_management/__init__.py
create mode 100644 tests/api_resources/verification_management/test_sandbox.py
diff --git a/.stats.yml b/.stats.yml
index b57e87a9..cce5f555 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 19
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/prelude/prelude-86fc45f417e6468f6a6087f9e35b264b0a350e31385bc078a2027a1f1abd1d15.yml
+configured_endpoints: 27
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/prelude/prelude-33b70f86404b99046db5c61d3142b149dd7dec7eefe5e62ff3b397c7274aa5dc.yml
openapi_spec_hash: fb72aba38ec55a3b6b5162c9f5b4228d
-config_hash: 3dbe79c14ecd9a153418249f9155d159
+config_hash: 707d65d2f456a2b5ba5b2c565b60128a
diff --git a/api.md b/api.md
index e6cf4924..8663a685 100644
--- a/api.md
+++ b/api.md
@@ -1,3 +1,9 @@
+# Shared Types
+
+```python
+from prelude_python_sdk.types import Signals, Target
+```
+
# Lookup
Types:
@@ -21,6 +27,7 @@ from prelude_python_sdk.types import (
NotifyListSubscriptionConfigsResponse,
NotifyListSubscriptionPhoneNumberEventsResponse,
NotifyListSubscriptionPhoneNumbersResponse,
+ NotifyReplyResponse,
NotifySendResponse,
NotifySendBatchResponse,
)
@@ -33,6 +40,7 @@ Methods:
- client.notify.list_subscription_configs(\*\*params) -> NotifyListSubscriptionConfigsResponse
- client.notify.list_subscription_phone_number_events(phone_number, \*, config_id, \*\*params) -> NotifyListSubscriptionPhoneNumberEventsResponse
- client.notify.list_subscription_phone_numbers(config_id, \*\*params) -> NotifyListSubscriptionPhoneNumbersResponse
+- client.notify.reply(\*\*params) -> NotifyReplyResponse
- client.notify.send(\*\*params) -> NotifySendResponse
- client.notify.send_batch(\*\*params) -> NotifySendBatchResponse
@@ -58,8 +66,29 @@ from prelude_python_sdk.types import VerificationCreateResponse, VerificationChe
Methods:
-- client.verification.create(\*\*params) -> VerificationCreateResponse
-- client.verification.check(\*\*params) -> VerificationCheckResponse
+- client.verification.create(\*\*params) -> VerificationCreateResponse
+- client.verification.check(\*\*params) -> VerificationCheckResponse
+
+## Phone
+
+### History
+
+Types:
+
+```python
+from prelude_python_sdk.types.verification.phone import (
+ PhoneVerificationCarrier,
+ PhoneVerificationMoney,
+ PhoneVerificationPsd2Transaction,
+ HistoryRetrieveResponse,
+ HistoryListResponse,
+)
+```
+
+Methods:
+
+- client.verification.phone.history.retrieve(id) -> HistoryRetrieveResponse
+- client.verification.phone.history.list(\*\*params) -> HistoryListResponse
# VerificationManagement
@@ -77,11 +106,29 @@ from prelude_python_sdk.types import (
Methods:
-- client.verification_management.delete_phone_number(action, \*\*params) -> VerificationManagementDeletePhoneNumberResponse
-- client.verification_management.list_phone_numbers(action) -> VerificationManagementListPhoneNumbersResponse
-- client.verification_management.list_sender_ids() -> VerificationManagementListSenderIDsResponse
-- client.verification_management.set_phone_number(action, \*\*params) -> VerificationManagementSetPhoneNumberResponse
-- client.verification_management.submit_sender_id(\*\*params) -> VerificationManagementSubmitSenderIDResponse
+- client.verification_management.delete_phone_number(action, \*\*params) -> VerificationManagementDeletePhoneNumberResponse
+- client.verification_management.list_phone_numbers(action) -> VerificationManagementListPhoneNumbersResponse
+- client.verification_management.list_sender_ids() -> VerificationManagementListSenderIDsResponse
+- client.verification_management.set_phone_number(action, \*\*params) -> VerificationManagementSetPhoneNumberResponse
+- client.verification_management.submit_sender_id(\*\*params) -> VerificationManagementSubmitSenderIDResponse
+
+## Sandbox
+
+Types:
+
+```python
+from prelude_python_sdk.types.verification_management import (
+ SandboxAddPhoneNumberResponse,
+ SandboxDeletePhoneNumberResponse,
+ SandboxListPhoneNumbersResponse,
+)
+```
+
+Methods:
+
+- client.verification_management.sandbox.add_phone_number(\*\*params) -> SandboxAddPhoneNumberResponse
+- client.verification_management.sandbox.delete_phone_number(phone_number) -> SandboxDeletePhoneNumberResponse
+- client.verification_management.sandbox.list_phone_numbers() -> SandboxListPhoneNumbersResponse
# Watch
@@ -89,6 +136,7 @@ Types:
```python
from prelude_python_sdk.types import (
+ WatchEvaluateResponse,
WatchPredictResponse,
WatchSendEventsResponse,
WatchSendFeedbacksResponse,
@@ -97,6 +145,21 @@ from prelude_python_sdk.types import (
Methods:
+- client.watch.evaluate(\*\*params) -> WatchEvaluateResponse
- client.watch.predict(\*\*params) -> WatchPredictResponse
- client.watch.send_events(\*\*params) -> WatchSendEventsResponse
- client.watch.send_feedbacks(\*\*params) -> WatchSendFeedbacksResponse
+
+# Intel
+
+## KYC
+
+Types:
+
+```python
+from prelude_python_sdk.types.intel import KYCMatchResponse
+```
+
+Methods:
+
+- client.intel.kyc.match(phone, \*\*params) -> KYCMatchResponse
diff --git a/src/prelude_python_sdk/_client.py b/src/prelude_python_sdk/_client.py
index 5a0a6dc4..cd9567ef 100644
--- a/src/prelude_python_sdk/_client.py
+++ b/src/prelude_python_sdk/_client.py
@@ -35,13 +35,17 @@
)
if TYPE_CHECKING:
- from .resources import watch, lookup, notify, verification, transactional, verification_management
+ from .resources import intel, watch, lookup, notify, verification, transactional, verification_management
from .resources.watch import WatchResource, AsyncWatchResource
from .resources.lookup import LookupResource, AsyncLookupResource
from .resources.notify import NotifyResource, AsyncNotifyResource
- from .resources.verification import VerificationResource, AsyncVerificationResource
+ from .resources.intel.intel import IntelResource, AsyncIntelResource
from .resources.transactional import TransactionalResource, AsyncTransactionalResource
- from .resources.verification_management import VerificationManagementResource, AsyncVerificationManagementResource
+ from .resources.verification.verification import VerificationResource, AsyncVerificationResource
+ from .resources.verification_management.verification_management import (
+ VerificationManagementResource,
+ AsyncVerificationManagementResource,
+ )
__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Prelude", "AsyncPrelude", "Client", "AsyncClient"]
@@ -154,6 +158,12 @@ def watch(self) -> WatchResource:
return WatchResource(self)
+ @cached_property
+ def intel(self) -> IntelResource:
+ from .resources.intel import IntelResource
+
+ return IntelResource(self)
+
@cached_property
def with_raw_response(self) -> PreludeWithRawResponse:
return PreludeWithRawResponse(self)
@@ -375,6 +385,12 @@ def watch(self) -> AsyncWatchResource:
return AsyncWatchResource(self)
+ @cached_property
+ def intel(self) -> AsyncIntelResource:
+ from .resources.intel import AsyncIntelResource
+
+ return AsyncIntelResource(self)
+
@cached_property
def with_raw_response(self) -> AsyncPreludeWithRawResponse:
return AsyncPreludeWithRawResponse(self)
@@ -538,6 +554,12 @@ def watch(self) -> watch.WatchResourceWithRawResponse:
return WatchResourceWithRawResponse(self._client.watch)
+ @cached_property
+ def intel(self) -> intel.IntelResourceWithRawResponse:
+ from .resources.intel import IntelResourceWithRawResponse
+
+ return IntelResourceWithRawResponse(self._client.intel)
+
class AsyncPreludeWithRawResponse:
_client: AsyncPrelude
@@ -589,6 +611,12 @@ def watch(self) -> watch.AsyncWatchResourceWithRawResponse:
return AsyncWatchResourceWithRawResponse(self._client.watch)
+ @cached_property
+ def intel(self) -> intel.AsyncIntelResourceWithRawResponse:
+ from .resources.intel import AsyncIntelResourceWithRawResponse
+
+ return AsyncIntelResourceWithRawResponse(self._client.intel)
+
class PreludeWithStreamedResponse:
_client: Prelude
@@ -640,6 +668,12 @@ def watch(self) -> watch.WatchResourceWithStreamingResponse:
return WatchResourceWithStreamingResponse(self._client.watch)
+ @cached_property
+ def intel(self) -> intel.IntelResourceWithStreamingResponse:
+ from .resources.intel import IntelResourceWithStreamingResponse
+
+ return IntelResourceWithStreamingResponse(self._client.intel)
+
class AsyncPreludeWithStreamedResponse:
_client: AsyncPrelude
@@ -693,6 +727,12 @@ def watch(self) -> watch.AsyncWatchResourceWithStreamingResponse:
return AsyncWatchResourceWithStreamingResponse(self._client.watch)
+ @cached_property
+ def intel(self) -> intel.AsyncIntelResourceWithStreamingResponse:
+ from .resources.intel import AsyncIntelResourceWithStreamingResponse
+
+ return AsyncIntelResourceWithStreamingResponse(self._client.intel)
+
Client = Prelude
diff --git a/src/prelude_python_sdk/resources/__init__.py b/src/prelude_python_sdk/resources/__init__.py
index 0699078d..662b6c3f 100644
--- a/src/prelude_python_sdk/resources/__init__.py
+++ b/src/prelude_python_sdk/resources/__init__.py
@@ -1,5 +1,13 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from .intel import (
+ IntelResource,
+ AsyncIntelResource,
+ IntelResourceWithRawResponse,
+ AsyncIntelResourceWithRawResponse,
+ IntelResourceWithStreamingResponse,
+ AsyncIntelResourceWithStreamingResponse,
+)
from .watch import (
WatchResource,
AsyncWatchResource,
@@ -86,4 +94,10 @@
"AsyncWatchResourceWithRawResponse",
"WatchResourceWithStreamingResponse",
"AsyncWatchResourceWithStreamingResponse",
+ "IntelResource",
+ "AsyncIntelResource",
+ "IntelResourceWithRawResponse",
+ "AsyncIntelResourceWithRawResponse",
+ "IntelResourceWithStreamingResponse",
+ "AsyncIntelResourceWithStreamingResponse",
]
diff --git a/src/prelude_python_sdk/resources/intel/__init__.py b/src/prelude_python_sdk/resources/intel/__init__.py
new file mode 100644
index 00000000..741e17ac
--- /dev/null
+++ b/src/prelude_python_sdk/resources/intel/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .kyc import (
+ KYCResource,
+ AsyncKYCResource,
+ KYCResourceWithRawResponse,
+ AsyncKYCResourceWithRawResponse,
+ KYCResourceWithStreamingResponse,
+ AsyncKYCResourceWithStreamingResponse,
+)
+from .intel import (
+ IntelResource,
+ AsyncIntelResource,
+ IntelResourceWithRawResponse,
+ AsyncIntelResourceWithRawResponse,
+ IntelResourceWithStreamingResponse,
+ AsyncIntelResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "KYCResource",
+ "AsyncKYCResource",
+ "KYCResourceWithRawResponse",
+ "AsyncKYCResourceWithRawResponse",
+ "KYCResourceWithStreamingResponse",
+ "AsyncKYCResourceWithStreamingResponse",
+ "IntelResource",
+ "AsyncIntelResource",
+ "IntelResourceWithRawResponse",
+ "AsyncIntelResourceWithRawResponse",
+ "IntelResourceWithStreamingResponse",
+ "AsyncIntelResourceWithStreamingResponse",
+]
diff --git a/src/prelude_python_sdk/resources/intel/intel.py b/src/prelude_python_sdk/resources/intel/intel.py
new file mode 100644
index 00000000..2d90dbc6
--- /dev/null
+++ b/src/prelude_python_sdk/resources/intel/intel.py
@@ -0,0 +1,120 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .kyc import (
+ KYCResource,
+ AsyncKYCResource,
+ KYCResourceWithRawResponse,
+ AsyncKYCResourceWithRawResponse,
+ KYCResourceWithStreamingResponse,
+ AsyncKYCResourceWithStreamingResponse,
+)
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+
+__all__ = ["IntelResource", "AsyncIntelResource"]
+
+
+class IntelResource(SyncAPIResource):
+ @cached_property
+ def kyc(self) -> KYCResource:
+ """
+ Retrieve detailed information about a phone number including carrier data, line type, and portability status.
+ """
+ return KYCResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> IntelResourceWithRawResponse:
+ """
+ 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/prelude-so/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return IntelResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> IntelResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/prelude-so/python-sdk#with_streaming_response
+ """
+ return IntelResourceWithStreamingResponse(self)
+
+
+class AsyncIntelResource(AsyncAPIResource):
+ @cached_property
+ def kyc(self) -> AsyncKYCResource:
+ """
+ Retrieve detailed information about a phone number including carrier data, line type, and portability status.
+ """
+ return AsyncKYCResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncIntelResourceWithRawResponse:
+ """
+ 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/prelude-so/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncIntelResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncIntelResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/prelude-so/python-sdk#with_streaming_response
+ """
+ return AsyncIntelResourceWithStreamingResponse(self)
+
+
+class IntelResourceWithRawResponse:
+ def __init__(self, intel: IntelResource) -> None:
+ self._intel = intel
+
+ @cached_property
+ def kyc(self) -> KYCResourceWithRawResponse:
+ """
+ Retrieve detailed information about a phone number including carrier data, line type, and portability status.
+ """
+ return KYCResourceWithRawResponse(self._intel.kyc)
+
+
+class AsyncIntelResourceWithRawResponse:
+ def __init__(self, intel: AsyncIntelResource) -> None:
+ self._intel = intel
+
+ @cached_property
+ def kyc(self) -> AsyncKYCResourceWithRawResponse:
+ """
+ Retrieve detailed information about a phone number including carrier data, line type, and portability status.
+ """
+ return AsyncKYCResourceWithRawResponse(self._intel.kyc)
+
+
+class IntelResourceWithStreamingResponse:
+ def __init__(self, intel: IntelResource) -> None:
+ self._intel = intel
+
+ @cached_property
+ def kyc(self) -> KYCResourceWithStreamingResponse:
+ """
+ Retrieve detailed information about a phone number including carrier data, line type, and portability status.
+ """
+ return KYCResourceWithStreamingResponse(self._intel.kyc)
+
+
+class AsyncIntelResourceWithStreamingResponse:
+ def __init__(self, intel: AsyncIntelResource) -> None:
+ self._intel = intel
+
+ @cached_property
+ def kyc(self) -> AsyncKYCResourceWithStreamingResponse:
+ """
+ Retrieve detailed information about a phone number including carrier data, line type, and portability status.
+ """
+ return AsyncKYCResourceWithStreamingResponse(self._intel.kyc)
diff --git a/src/prelude_python_sdk/resources/intel/kyc.py b/src/prelude_python_sdk/resources/intel/kyc.py
new file mode 100644
index 00000000..c2caa9ca
--- /dev/null
+++ b/src/prelude_python_sdk/resources/intel/kyc.py
@@ -0,0 +1,270 @@
+# 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 ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...types.intel import kyc_match_params
+from ..._base_client import make_request_options
+from ...types.intel.kyc_match_response import KYCMatchResponse
+
+__all__ = ["KYCResource", "AsyncKYCResource"]
+
+
+class KYCResource(SyncAPIResource):
+ """
+ Retrieve detailed information about a phone number including carrier data, line type, and portability status.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> KYCResourceWithRawResponse:
+ """
+ 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/prelude-so/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return KYCResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> KYCResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/prelude-so/python-sdk#with_streaming_response
+ """
+ return KYCResourceWithStreamingResponse(self)
+
+ def match(
+ self,
+ phone: str,
+ *,
+ address: str | Omit = omit,
+ birthdate: Union[str, date] | Omit = omit,
+ country: str | Omit = omit,
+ email: str | Omit = omit,
+ family_name: str | Omit = omit,
+ given_name: str | Omit = omit,
+ locality: str | Omit = omit,
+ postal_code: str | Omit = omit,
+ region: 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> KYCMatchResponse:
+ """
+ Verify identity attributes against the subscriber record held by the end-user's
+ mobile operator. Send a phone number along with the attributes to check; Prelude
+ resolves the operator internally and returns a per-attribute match. Currently
+ available for France only (Orange, SFR, Bouygues) and must be enabled for your
+ account.
+
+ Args:
+ phone: An E.164 formatted phone number whose subscriber identity to match against.
+
+ address: The street address.
+
+ birthdate: The date of birth in ISO 8601 (`YYYY-MM-DD`) format. Compared exactly.
+
+ country: The ISO 3166-1 alpha-2 country code. Compared exactly.
+
+ email: The email address.
+
+ family_name: The end-user's family (last) name.
+
+ given_name: The end-user's given (first) name.
+
+ locality: The locality (city).
+
+ postal_code: The postal code. Compared exactly.
+
+ region: The region, state, or province.
+
+ 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 phone:
+ raise ValueError(f"Expected a non-empty value for `phone` but received {phone!r}")
+ return self._post(
+ path_template("/v2/intel/kyc/match/{phone}", phone=phone),
+ body=maybe_transform(
+ {
+ "address": address,
+ "birthdate": birthdate,
+ "country": country,
+ "email": email,
+ "family_name": family_name,
+ "given_name": given_name,
+ "locality": locality,
+ "postal_code": postal_code,
+ "region": region,
+ },
+ kyc_match_params.KYCMatchParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=KYCMatchResponse,
+ )
+
+
+class AsyncKYCResource(AsyncAPIResource):
+ """
+ Retrieve detailed information about a phone number including carrier data, line type, and portability status.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncKYCResourceWithRawResponse:
+ """
+ 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/prelude-so/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncKYCResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncKYCResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/prelude-so/python-sdk#with_streaming_response
+ """
+ return AsyncKYCResourceWithStreamingResponse(self)
+
+ async def match(
+ self,
+ phone: str,
+ *,
+ address: str | Omit = omit,
+ birthdate: Union[str, date] | Omit = omit,
+ country: str | Omit = omit,
+ email: str | Omit = omit,
+ family_name: str | Omit = omit,
+ given_name: str | Omit = omit,
+ locality: str | Omit = omit,
+ postal_code: str | Omit = omit,
+ region: 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> KYCMatchResponse:
+ """
+ Verify identity attributes against the subscriber record held by the end-user's
+ mobile operator. Send a phone number along with the attributes to check; Prelude
+ resolves the operator internally and returns a per-attribute match. Currently
+ available for France only (Orange, SFR, Bouygues) and must be enabled for your
+ account.
+
+ Args:
+ phone: An E.164 formatted phone number whose subscriber identity to match against.
+
+ address: The street address.
+
+ birthdate: The date of birth in ISO 8601 (`YYYY-MM-DD`) format. Compared exactly.
+
+ country: The ISO 3166-1 alpha-2 country code. Compared exactly.
+
+ email: The email address.
+
+ family_name: The end-user's family (last) name.
+
+ given_name: The end-user's given (first) name.
+
+ locality: The locality (city).
+
+ postal_code: The postal code. Compared exactly.
+
+ region: The region, state, or province.
+
+ 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 phone:
+ raise ValueError(f"Expected a non-empty value for `phone` but received {phone!r}")
+ return await self._post(
+ path_template("/v2/intel/kyc/match/{phone}", phone=phone),
+ body=await async_maybe_transform(
+ {
+ "address": address,
+ "birthdate": birthdate,
+ "country": country,
+ "email": email,
+ "family_name": family_name,
+ "given_name": given_name,
+ "locality": locality,
+ "postal_code": postal_code,
+ "region": region,
+ },
+ kyc_match_params.KYCMatchParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=KYCMatchResponse,
+ )
+
+
+class KYCResourceWithRawResponse:
+ def __init__(self, kyc: KYCResource) -> None:
+ self._kyc = kyc
+
+ self.match = to_raw_response_wrapper(
+ kyc.match,
+ )
+
+
+class AsyncKYCResourceWithRawResponse:
+ def __init__(self, kyc: AsyncKYCResource) -> None:
+ self._kyc = kyc
+
+ self.match = async_to_raw_response_wrapper(
+ kyc.match,
+ )
+
+
+class KYCResourceWithStreamingResponse:
+ def __init__(self, kyc: KYCResource) -> None:
+ self._kyc = kyc
+
+ self.match = to_streamed_response_wrapper(
+ kyc.match,
+ )
+
+
+class AsyncKYCResourceWithStreamingResponse:
+ def __init__(self, kyc: AsyncKYCResource) -> None:
+ self._kyc = kyc
+
+ self.match = async_to_streamed_response_wrapper(
+ kyc.match,
+ )
diff --git a/src/prelude_python_sdk/resources/notify.py b/src/prelude_python_sdk/resources/notify.py
index 5cf6e38a..23e04473 100644
--- a/src/prelude_python_sdk/resources/notify.py
+++ b/src/prelude_python_sdk/resources/notify.py
@@ -10,6 +10,7 @@
from ..types import (
notify_send_params,
+ notify_reply_params,
notify_send_batch_params,
notify_list_subscription_configs_params,
notify_list_subscription_phone_numbers_params,
@@ -27,6 +28,7 @@
)
from .._base_client import make_request_options
from ..types.notify_send_response import NotifySendResponse
+from ..types.notify_reply_response import NotifyReplyResponse
from ..types.notify_send_batch_response import NotifySendBatchResponse
from ..types.notify_get_subscription_config_response import NotifyGetSubscriptionConfigResponse
from ..types.notify_list_subscription_configs_response import NotifyListSubscriptionConfigsResponse
@@ -310,6 +312,66 @@ def list_subscription_phone_numbers(
cast_to=NotifyListSubscriptionPhoneNumbersResponse,
)
+ def reply(
+ self,
+ *,
+ reply_to: str,
+ text: str,
+ to: str,
+ callback_url: str | Omit = omit,
+ correlation_id: 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NotifyReplyResponse:
+ """
+ Send a free-form text reply to an inbound WhatsApp message within the 24-hour
+ conversation window. See
+ [WhatsApp 2-Way Messaging](/notify/v2/documentation/whatsapp) for details.
+
+ Args:
+ reply_to: The inbound message ID (prefixed with `im_`) to reply to. This ID is provided in
+ the `inbound.message.received` webhook event.
+
+ text: The reply message body sent as a free-form WhatsApp text.
+
+ to: The recipient's phone number in E.164 format. Must match the phone number that
+ sent the original inbound message.
+
+ callback_url: The URL where webhooks will be sent for delivery events of this reply.
+
+ correlation_id: A user-defined identifier to correlate this reply with your internal systems. It
+ is returned in the response and any webhook events that refer to this message.
+
+ 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
+ """
+ return self._post(
+ "/v2/notify/reply",
+ body=maybe_transform(
+ {
+ "reply_to": reply_to,
+ "text": text,
+ "to": to,
+ "callback_url": callback_url,
+ "correlation_id": correlation_id,
+ },
+ notify_reply_params.NotifyReplyParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NotifyReplyResponse,
+ )
+
def send(
self,
*,
@@ -786,6 +848,66 @@ async def list_subscription_phone_numbers(
cast_to=NotifyListSubscriptionPhoneNumbersResponse,
)
+ async def reply(
+ self,
+ *,
+ reply_to: str,
+ text: str,
+ to: str,
+ callback_url: str | Omit = omit,
+ correlation_id: 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NotifyReplyResponse:
+ """
+ Send a free-form text reply to an inbound WhatsApp message within the 24-hour
+ conversation window. See
+ [WhatsApp 2-Way Messaging](/notify/v2/documentation/whatsapp) for details.
+
+ Args:
+ reply_to: The inbound message ID (prefixed with `im_`) to reply to. This ID is provided in
+ the `inbound.message.received` webhook event.
+
+ text: The reply message body sent as a free-form WhatsApp text.
+
+ to: The recipient's phone number in E.164 format. Must match the phone number that
+ sent the original inbound message.
+
+ callback_url: The URL where webhooks will be sent for delivery events of this reply.
+
+ correlation_id: A user-defined identifier to correlate this reply with your internal systems. It
+ is returned in the response and any webhook events that refer to this message.
+
+ 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
+ """
+ return await self._post(
+ "/v2/notify/reply",
+ body=await async_maybe_transform(
+ {
+ "reply_to": reply_to,
+ "text": text,
+ "to": to,
+ "callback_url": callback_url,
+ "correlation_id": correlation_id,
+ },
+ notify_reply_params.NotifyReplyParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NotifyReplyResponse,
+ )
+
async def send(
self,
*,
@@ -1010,6 +1132,9 @@ def __init__(self, notify: NotifyResource) -> None:
self.list_subscription_phone_numbers = to_raw_response_wrapper(
notify.list_subscription_phone_numbers,
)
+ self.reply = to_raw_response_wrapper(
+ notify.reply,
+ )
self.send = to_raw_response_wrapper(
notify.send,
)
@@ -1037,6 +1162,9 @@ def __init__(self, notify: AsyncNotifyResource) -> None:
self.list_subscription_phone_numbers = async_to_raw_response_wrapper(
notify.list_subscription_phone_numbers,
)
+ self.reply = async_to_raw_response_wrapper(
+ notify.reply,
+ )
self.send = async_to_raw_response_wrapper(
notify.send,
)
@@ -1064,6 +1192,9 @@ def __init__(self, notify: NotifyResource) -> None:
self.list_subscription_phone_numbers = to_streamed_response_wrapper(
notify.list_subscription_phone_numbers,
)
+ self.reply = to_streamed_response_wrapper(
+ notify.reply,
+ )
self.send = to_streamed_response_wrapper(
notify.send,
)
@@ -1091,6 +1222,9 @@ def __init__(self, notify: AsyncNotifyResource) -> None:
self.list_subscription_phone_numbers = async_to_streamed_response_wrapper(
notify.list_subscription_phone_numbers,
)
+ self.reply = async_to_streamed_response_wrapper(
+ notify.reply,
+ )
self.send = async_to_streamed_response_wrapper(
notify.send,
)
diff --git a/src/prelude_python_sdk/resources/verification/__init__.py b/src/prelude_python_sdk/resources/verification/__init__.py
new file mode 100644
index 00000000..81689f4e
--- /dev/null
+++ b/src/prelude_python_sdk/resources/verification/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .phone import (
+ PhoneResource,
+ AsyncPhoneResource,
+ PhoneResourceWithRawResponse,
+ AsyncPhoneResourceWithRawResponse,
+ PhoneResourceWithStreamingResponse,
+ AsyncPhoneResourceWithStreamingResponse,
+)
+from .verification import (
+ VerificationResource,
+ AsyncVerificationResource,
+ VerificationResourceWithRawResponse,
+ AsyncVerificationResourceWithRawResponse,
+ VerificationResourceWithStreamingResponse,
+ AsyncVerificationResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "PhoneResource",
+ "AsyncPhoneResource",
+ "PhoneResourceWithRawResponse",
+ "AsyncPhoneResourceWithRawResponse",
+ "PhoneResourceWithStreamingResponse",
+ "AsyncPhoneResourceWithStreamingResponse",
+ "VerificationResource",
+ "AsyncVerificationResource",
+ "VerificationResourceWithRawResponse",
+ "AsyncVerificationResourceWithRawResponse",
+ "VerificationResourceWithStreamingResponse",
+ "AsyncVerificationResourceWithStreamingResponse",
+]
diff --git a/src/prelude_python_sdk/resources/verification/phone/__init__.py b/src/prelude_python_sdk/resources/verification/phone/__init__.py
new file mode 100644
index 00000000..d6d3cba6
--- /dev/null
+++ b/src/prelude_python_sdk/resources/verification/phone/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .phone import (
+ PhoneResource,
+ AsyncPhoneResource,
+ PhoneResourceWithRawResponse,
+ AsyncPhoneResourceWithRawResponse,
+ PhoneResourceWithStreamingResponse,
+ AsyncPhoneResourceWithStreamingResponse,
+)
+from .history import (
+ HistoryResource,
+ AsyncHistoryResource,
+ HistoryResourceWithRawResponse,
+ AsyncHistoryResourceWithRawResponse,
+ HistoryResourceWithStreamingResponse,
+ AsyncHistoryResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "HistoryResource",
+ "AsyncHistoryResource",
+ "HistoryResourceWithRawResponse",
+ "AsyncHistoryResourceWithRawResponse",
+ "HistoryResourceWithStreamingResponse",
+ "AsyncHistoryResourceWithStreamingResponse",
+ "PhoneResource",
+ "AsyncPhoneResource",
+ "PhoneResourceWithRawResponse",
+ "AsyncPhoneResourceWithRawResponse",
+ "PhoneResourceWithStreamingResponse",
+ "AsyncPhoneResourceWithStreamingResponse",
+]
diff --git a/src/prelude_python_sdk/resources/verification/phone/history.py b/src/prelude_python_sdk/resources/verification/phone/history.py
new file mode 100644
index 00000000..ea8dc483
--- /dev/null
+++ b/src/prelude_python_sdk/resources/verification/phone/history.py
@@ -0,0 +1,436 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Union
+from datetime import datetime
+from typing_extensions import Literal
+
+import httpx
+
+from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ...._utils import path_template, maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...._base_client import make_request_options
+from ....types.verification.phone import history_list_params
+from ....types.verification.phone.history_list_response import HistoryListResponse
+from ....types.verification.phone.history_retrieve_response import HistoryRetrieveResponse
+
+__all__ = ["HistoryResource", "AsyncHistoryResource"]
+
+
+class HistoryResource(SyncAPIResource):
+ """Verify phone numbers."""
+
+ @cached_property
+ def with_raw_response(self) -> HistoryResourceWithRawResponse:
+ """
+ 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/prelude-so/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return HistoryResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> HistoryResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/prelude-so/python-sdk#with_streaming_response
+ """
+ return HistoryResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ id: 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,
+ ) -> HistoryRetrieveResponse:
+ """
+ Retrieve everything Prelude recorded for one phone verification: its outcome and
+ the device, network and anti-fraud context it was created in, the chronological
+ timeline of every message attempt and code check, and the anti-fraud signals you
+ forwarded.
+
+ The identifier is the `id` returned by
+ [Create or retry a verification](/verify/v2/api-reference/create-or-retry-a-verification)
+ or the `verification_id` of the verification webhooks. Both `lifecycle` and
+ `signals` are optional: a verification can resolve with its top-level fields
+ alone.
+
+ 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 id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._get(
+ path_template("/v2/verification/phone/history/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=HistoryRetrieveResponse,
+ )
+
+ def list(
+ self,
+ *,
+ channels: List[Literal["sms", "rcs", "whatsapp", "viber", "zalo", "telegram", "voice", "silent"]] | Omit = omit,
+ cursor: str | Omit = omit,
+ device_platform: Literal["android", "ios", "ipados", "tvos", "web"] | Omit = omit,
+ from_: Union[str, datetime] | Omit = omit,
+ limit: int | Omit = omit,
+ max_attempts: int | Omit = omit,
+ min_attempts: int | Omit = omit,
+ phone_number: str | Omit = omit,
+ region: str | Omit = omit,
+ status: Literal[
+ "converted",
+ "not_converted",
+ "pending_check",
+ "sent",
+ "challenged",
+ "suspected_fraud",
+ "in_blocklist",
+ "invalid_line",
+ "invalid_number",
+ "rate_limited",
+ "expired_signals",
+ "shadowed",
+ ]
+ | Omit = omit,
+ template_id: str | Omit = omit,
+ to: Union[str, datetime] | 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,
+ ) -> HistoryListResponse:
+ """
+ List your phone verifications, most recent first, one entry per verification
+ with its outcome, channels, attempts and cost. Every filter is optional and they
+ combine with AND.
+
+ Use it to find every verification a phone number went through from your support
+ tooling, then
+ [Get a phone verification](/verify/v2/api-reference/history/get-a-phone-verification)
+ for the full timeline of one of them. A cursor is bound to the filters that
+ produced it: pass `next_cursor` back with the exact same query parameters.
+
+ Args:
+ channels: Only verifications that could use one of these channels. Repeat the parameter
+ for several values.
+
+ cursor: Pagination cursor from the previous response.
+
+ device_platform: Only verifications created from this device platform.
+
+ from_: Only verifications created at or after this RFC 3339 timestamp. Goes with `to`,
+ at most 6 months apart. Without them the whole history is searched.
+
+ limit: Maximum number of verifications to return per page.
+
+ max_attempts: Only verifications that sent at most this many messages. `0` keeps the
+ verifications that never sent one.
+
+ min_attempts: Only verifications that sent at least this many messages.
+
+ phone_number: Only verifications targeting this E.164 phone number. The leading `+` may be
+ omitted.
+
+ region: Only verifications of phone numbers from this region, as an ISO 3166-1 alpha-2
+ code.
+
+ status: Only verifications in this status. `pending_check` cannot be filtered on.
+
+ template_id: Only verifications sent with this template, as returned in `template_id` by
+ [Get a phone verification](/verify/v2/api-reference/history/get-a-phone-verification).
+ Built-in templates (`prelude:*`) cannot be filtered on.
+
+ to: Only verifications created at or before this RFC 3339 timestamp. Goes with
+ `from`.
+
+ 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
+ """
+ return self._get(
+ "/v2/verification/phone/history",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "channels": channels,
+ "cursor": cursor,
+ "device_platform": device_platform,
+ "from_": from_,
+ "limit": limit,
+ "max_attempts": max_attempts,
+ "min_attempts": min_attempts,
+ "phone_number": phone_number,
+ "region": region,
+ "status": status,
+ "template_id": template_id,
+ "to": to,
+ },
+ history_list_params.HistoryListParams,
+ ),
+ ),
+ cast_to=HistoryListResponse,
+ )
+
+
+class AsyncHistoryResource(AsyncAPIResource):
+ """Verify phone numbers."""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncHistoryResourceWithRawResponse:
+ """
+ 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/prelude-so/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncHistoryResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncHistoryResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/prelude-so/python-sdk#with_streaming_response
+ """
+ return AsyncHistoryResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ id: 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,
+ ) -> HistoryRetrieveResponse:
+ """
+ Retrieve everything Prelude recorded for one phone verification: its outcome and
+ the device, network and anti-fraud context it was created in, the chronological
+ timeline of every message attempt and code check, and the anti-fraud signals you
+ forwarded.
+
+ The identifier is the `id` returned by
+ [Create or retry a verification](/verify/v2/api-reference/create-or-retry-a-verification)
+ or the `verification_id` of the verification webhooks. Both `lifecycle` and
+ `signals` are optional: a verification can resolve with its top-level fields
+ alone.
+
+ 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 id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._get(
+ path_template("/v2/verification/phone/history/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=HistoryRetrieveResponse,
+ )
+
+ async def list(
+ self,
+ *,
+ channels: List[Literal["sms", "rcs", "whatsapp", "viber", "zalo", "telegram", "voice", "silent"]] | Omit = omit,
+ cursor: str | Omit = omit,
+ device_platform: Literal["android", "ios", "ipados", "tvos", "web"] | Omit = omit,
+ from_: Union[str, datetime] | Omit = omit,
+ limit: int | Omit = omit,
+ max_attempts: int | Omit = omit,
+ min_attempts: int | Omit = omit,
+ phone_number: str | Omit = omit,
+ region: str | Omit = omit,
+ status: Literal[
+ "converted",
+ "not_converted",
+ "pending_check",
+ "sent",
+ "challenged",
+ "suspected_fraud",
+ "in_blocklist",
+ "invalid_line",
+ "invalid_number",
+ "rate_limited",
+ "expired_signals",
+ "shadowed",
+ ]
+ | Omit = omit,
+ template_id: str | Omit = omit,
+ to: Union[str, datetime] | 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,
+ ) -> HistoryListResponse:
+ """
+ List your phone verifications, most recent first, one entry per verification
+ with its outcome, channels, attempts and cost. Every filter is optional and they
+ combine with AND.
+
+ Use it to find every verification a phone number went through from your support
+ tooling, then
+ [Get a phone verification](/verify/v2/api-reference/history/get-a-phone-verification)
+ for the full timeline of one of them. A cursor is bound to the filters that
+ produced it: pass `next_cursor` back with the exact same query parameters.
+
+ Args:
+ channels: Only verifications that could use one of these channels. Repeat the parameter
+ for several values.
+
+ cursor: Pagination cursor from the previous response.
+
+ device_platform: Only verifications created from this device platform.
+
+ from_: Only verifications created at or after this RFC 3339 timestamp. Goes with `to`,
+ at most 6 months apart. Without them the whole history is searched.
+
+ limit: Maximum number of verifications to return per page.
+
+ max_attempts: Only verifications that sent at most this many messages. `0` keeps the
+ verifications that never sent one.
+
+ min_attempts: Only verifications that sent at least this many messages.
+
+ phone_number: Only verifications targeting this E.164 phone number. The leading `+` may be
+ omitted.
+
+ region: Only verifications of phone numbers from this region, as an ISO 3166-1 alpha-2
+ code.
+
+ status: Only verifications in this status. `pending_check` cannot be filtered on.
+
+ template_id: Only verifications sent with this template, as returned in `template_id` by
+ [Get a phone verification](/verify/v2/api-reference/history/get-a-phone-verification).
+ Built-in templates (`prelude:*`) cannot be filtered on.
+
+ to: Only verifications created at or before this RFC 3339 timestamp. Goes with
+ `from`.
+
+ 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
+ """
+ return await self._get(
+ "/v2/verification/phone/history",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "channels": channels,
+ "cursor": cursor,
+ "device_platform": device_platform,
+ "from_": from_,
+ "limit": limit,
+ "max_attempts": max_attempts,
+ "min_attempts": min_attempts,
+ "phone_number": phone_number,
+ "region": region,
+ "status": status,
+ "template_id": template_id,
+ "to": to,
+ },
+ history_list_params.HistoryListParams,
+ ),
+ ),
+ cast_to=HistoryListResponse,
+ )
+
+
+class HistoryResourceWithRawResponse:
+ def __init__(self, history: HistoryResource) -> None:
+ self._history = history
+
+ self.retrieve = to_raw_response_wrapper(
+ history.retrieve,
+ )
+ self.list = to_raw_response_wrapper(
+ history.list,
+ )
+
+
+class AsyncHistoryResourceWithRawResponse:
+ def __init__(self, history: AsyncHistoryResource) -> None:
+ self._history = history
+
+ self.retrieve = async_to_raw_response_wrapper(
+ history.retrieve,
+ )
+ self.list = async_to_raw_response_wrapper(
+ history.list,
+ )
+
+
+class HistoryResourceWithStreamingResponse:
+ def __init__(self, history: HistoryResource) -> None:
+ self._history = history
+
+ self.retrieve = to_streamed_response_wrapper(
+ history.retrieve,
+ )
+ self.list = to_streamed_response_wrapper(
+ history.list,
+ )
+
+
+class AsyncHistoryResourceWithStreamingResponse:
+ def __init__(self, history: AsyncHistoryResource) -> None:
+ self._history = history
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ history.retrieve,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ history.list,
+ )
diff --git a/src/prelude_python_sdk/resources/verification/phone/phone.py b/src/prelude_python_sdk/resources/verification/phone/phone.py
new file mode 100644
index 00000000..bad92e9f
--- /dev/null
+++ b/src/prelude_python_sdk/resources/verification/phone/phone.py
@@ -0,0 +1,108 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .history import (
+ HistoryResource,
+ AsyncHistoryResource,
+ HistoryResourceWithRawResponse,
+ AsyncHistoryResourceWithRawResponse,
+ HistoryResourceWithStreamingResponse,
+ AsyncHistoryResourceWithStreamingResponse,
+)
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+
+__all__ = ["PhoneResource", "AsyncPhoneResource"]
+
+
+class PhoneResource(SyncAPIResource):
+ @cached_property
+ def history(self) -> HistoryResource:
+ """Verify phone numbers."""
+ return HistoryResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> PhoneResourceWithRawResponse:
+ """
+ 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/prelude-so/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return PhoneResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> PhoneResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/prelude-so/python-sdk#with_streaming_response
+ """
+ return PhoneResourceWithStreamingResponse(self)
+
+
+class AsyncPhoneResource(AsyncAPIResource):
+ @cached_property
+ def history(self) -> AsyncHistoryResource:
+ """Verify phone numbers."""
+ return AsyncHistoryResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncPhoneResourceWithRawResponse:
+ """
+ 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/prelude-so/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncPhoneResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncPhoneResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/prelude-so/python-sdk#with_streaming_response
+ """
+ return AsyncPhoneResourceWithStreamingResponse(self)
+
+
+class PhoneResourceWithRawResponse:
+ def __init__(self, phone: PhoneResource) -> None:
+ self._phone = phone
+
+ @cached_property
+ def history(self) -> HistoryResourceWithRawResponse:
+ """Verify phone numbers."""
+ return HistoryResourceWithRawResponse(self._phone.history)
+
+
+class AsyncPhoneResourceWithRawResponse:
+ def __init__(self, phone: AsyncPhoneResource) -> None:
+ self._phone = phone
+
+ @cached_property
+ def history(self) -> AsyncHistoryResourceWithRawResponse:
+ """Verify phone numbers."""
+ return AsyncHistoryResourceWithRawResponse(self._phone.history)
+
+
+class PhoneResourceWithStreamingResponse:
+ def __init__(self, phone: PhoneResource) -> None:
+ self._phone = phone
+
+ @cached_property
+ def history(self) -> HistoryResourceWithStreamingResponse:
+ """Verify phone numbers."""
+ return HistoryResourceWithStreamingResponse(self._phone.history)
+
+
+class AsyncPhoneResourceWithStreamingResponse:
+ def __init__(self, phone: AsyncPhoneResource) -> None:
+ self._phone = phone
+
+ @cached_property
+ def history(self) -> AsyncHistoryResourceWithStreamingResponse:
+ """Verify phone numbers."""
+ return AsyncHistoryResourceWithStreamingResponse(self._phone.history)
diff --git a/src/prelude_python_sdk/resources/verification.py b/src/prelude_python_sdk/resources/verification/verification.py
similarity index 87%
rename from src/prelude_python_sdk/resources/verification.py
rename to src/prelude_python_sdk/resources/verification/verification.py
index 80cb8abb..d836b088 100644
--- a/src/prelude_python_sdk/resources/verification.py
+++ b/src/prelude_python_sdk/resources/verification/verification.py
@@ -4,20 +4,30 @@
import httpx
-from ..types import verification_check_params, verification_create_params
-from .._types import Body, Omit, Query, Headers, 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 (
+from ...types import verification_check_params, verification_create_params
+from ..._types import Body, Omit, Query, Headers, 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_raw_response_wrapper,
to_streamed_response_wrapper,
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
-from .._base_client import make_request_options
-from ..types.verification_check_response import VerificationCheckResponse
-from ..types.verification_create_response import VerificationCreateResponse
+from .phone.phone import (
+ PhoneResource,
+ AsyncPhoneResource,
+ PhoneResourceWithRawResponse,
+ AsyncPhoneResourceWithRawResponse,
+ PhoneResourceWithStreamingResponse,
+ AsyncPhoneResourceWithStreamingResponse,
+)
+from ..._base_client import make_request_options
+from ...types.shared_params.target import Target
+from ...types.shared_params.signals import Signals
+from ...types.verification_check_response import VerificationCheckResponse
+from ...types.verification_create_response import VerificationCreateResponse
__all__ = ["VerificationResource", "AsyncVerificationResource"]
@@ -25,6 +35,10 @@
class VerificationResource(SyncAPIResource):
"""Verify phone numbers."""
+ @cached_property
+ def phone(self) -> PhoneResource:
+ return PhoneResource(self._client)
+
@cached_property
def with_raw_response(self) -> VerificationResourceWithRawResponse:
"""
@@ -47,11 +61,11 @@ def with_streaming_response(self) -> VerificationResourceWithStreamingResponse:
def create(
self,
*,
- target: verification_create_params.Target,
+ target: Target,
dispatch_id: str | Omit = omit,
metadata: verification_create_params.Metadata | Omit = omit,
options: verification_create_params.Options | Omit = omit,
- signals: verification_create_params.Signals | Omit = omit,
+ signals: Signals | 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,
@@ -109,7 +123,7 @@ def check(
self,
*,
code: str,
- target: verification_check_params.Target,
+ target: Target,
psd2: verification_check_params.Psd2 | 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.
@@ -160,6 +174,10 @@ def check(
class AsyncVerificationResource(AsyncAPIResource):
"""Verify phone numbers."""
+ @cached_property
+ def phone(self) -> AsyncPhoneResource:
+ return AsyncPhoneResource(self._client)
+
@cached_property
def with_raw_response(self) -> AsyncVerificationResourceWithRawResponse:
"""
@@ -182,11 +200,11 @@ def with_streaming_response(self) -> AsyncVerificationResourceWithStreamingRespo
async def create(
self,
*,
- target: verification_create_params.Target,
+ target: Target,
dispatch_id: str | Omit = omit,
metadata: verification_create_params.Metadata | Omit = omit,
options: verification_create_params.Options | Omit = omit,
- signals: verification_create_params.Signals | Omit = omit,
+ signals: Signals | 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,
@@ -244,7 +262,7 @@ async def check(
self,
*,
code: str,
- target: verification_check_params.Target,
+ target: Target,
psd2: verification_check_params.Psd2 | 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.
@@ -303,6 +321,10 @@ def __init__(self, verification: VerificationResource) -> None:
verification.check,
)
+ @cached_property
+ def phone(self) -> PhoneResourceWithRawResponse:
+ return PhoneResourceWithRawResponse(self._verification.phone)
+
class AsyncVerificationResourceWithRawResponse:
def __init__(self, verification: AsyncVerificationResource) -> None:
@@ -315,6 +337,10 @@ def __init__(self, verification: AsyncVerificationResource) -> None:
verification.check,
)
+ @cached_property
+ def phone(self) -> AsyncPhoneResourceWithRawResponse:
+ return AsyncPhoneResourceWithRawResponse(self._verification.phone)
+
class VerificationResourceWithStreamingResponse:
def __init__(self, verification: VerificationResource) -> None:
@@ -327,6 +353,10 @@ def __init__(self, verification: VerificationResource) -> None:
verification.check,
)
+ @cached_property
+ def phone(self) -> PhoneResourceWithStreamingResponse:
+ return PhoneResourceWithStreamingResponse(self._verification.phone)
+
class AsyncVerificationResourceWithStreamingResponse:
def __init__(self, verification: AsyncVerificationResource) -> None:
@@ -338,3 +368,7 @@ def __init__(self, verification: AsyncVerificationResource) -> None:
self.check = async_to_streamed_response_wrapper(
verification.check,
)
+
+ @cached_property
+ def phone(self) -> AsyncPhoneResourceWithStreamingResponse:
+ return AsyncPhoneResourceWithStreamingResponse(self._verification.phone)
diff --git a/src/prelude_python_sdk/resources/verification_management/__init__.py b/src/prelude_python_sdk/resources/verification_management/__init__.py
new file mode 100644
index 00000000..264240bc
--- /dev/null
+++ b/src/prelude_python_sdk/resources/verification_management/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .sandbox import (
+ SandboxResource,
+ AsyncSandboxResource,
+ SandboxResourceWithRawResponse,
+ AsyncSandboxResourceWithRawResponse,
+ SandboxResourceWithStreamingResponse,
+ AsyncSandboxResourceWithStreamingResponse,
+)
+from .verification_management import (
+ VerificationManagementResource,
+ AsyncVerificationManagementResource,
+ VerificationManagementResourceWithRawResponse,
+ AsyncVerificationManagementResourceWithRawResponse,
+ VerificationManagementResourceWithStreamingResponse,
+ AsyncVerificationManagementResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "SandboxResource",
+ "AsyncSandboxResource",
+ "SandboxResourceWithRawResponse",
+ "AsyncSandboxResourceWithRawResponse",
+ "SandboxResourceWithStreamingResponse",
+ "AsyncSandboxResourceWithStreamingResponse",
+ "VerificationManagementResource",
+ "AsyncVerificationManagementResource",
+ "VerificationManagementResourceWithRawResponse",
+ "AsyncVerificationManagementResourceWithRawResponse",
+ "VerificationManagementResourceWithStreamingResponse",
+ "AsyncVerificationManagementResourceWithStreamingResponse",
+]
diff --git a/src/prelude_python_sdk/resources/verification_management/sandbox.py b/src/prelude_python_sdk/resources/verification_management/sandbox.py
new file mode 100644
index 00000000..0a787d53
--- /dev/null
+++ b/src/prelude_python_sdk/resources/verification_management/sandbox.py
@@ -0,0 +1,363 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.verification_management import sandbox_add_phone_number_params
+from ...types.verification_management.sandbox_add_phone_number_response import SandboxAddPhoneNumberResponse
+from ...types.verification_management.sandbox_list_phone_numbers_response import SandboxListPhoneNumbersResponse
+from ...types.verification_management.sandbox_delete_phone_number_response import SandboxDeletePhoneNumberResponse
+
+__all__ = ["SandboxResource", "AsyncSandboxResource"]
+
+
+class SandboxResource(SyncAPIResource):
+ """Verify phone numbers."""
+
+ @cached_property
+ def with_raw_response(self) -> SandboxResourceWithRawResponse:
+ """
+ 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/prelude-so/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return SandboxResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> SandboxResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/prelude-so/python-sdk#with_streaming_response
+ """
+ return SandboxResourceWithStreamingResponse(self)
+
+ def add_phone_number(
+ self,
+ *,
+ attempt_code: str,
+ phone_number: 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,
+ ) -> SandboxAddPhoneNumberResponse:
+ """
+ Register a phone number as a sandbox number and associate it with a fixed
+ attempt code. Subsequent verification attempts against this number will not
+ trigger a real SMS/call and will validate against the configured attempt code.
+
+ This operation is idempotent - re-adding the same phone number will overwrite
+ the existing attempt code.
+
+ In order to get access to this endpoint, contact our support team.
+
+ Args:
+ attempt_code: The fixed attempt code that will validate verification attempts for this phone
+ number.
+
+ phone_number: An E.164 formatted phone number to add to the sandbox list.
+
+ 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
+ """
+ return self._put(
+ "/v2/verification/management/phone-numbers/sandbox",
+ body=maybe_transform(
+ {
+ "attempt_code": attempt_code,
+ "phone_number": phone_number,
+ },
+ sandbox_add_phone_number_params.SandboxAddPhoneNumberParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SandboxAddPhoneNumberResponse,
+ )
+
+ def delete_phone_number(
+ self,
+ phone_number: 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,
+ ) -> SandboxDeletePhoneNumberResponse:
+ """
+ Remove a phone number from the sandbox list.
+
+ This operation is idempotent - deleting a phone number that is not in the
+ sandbox list will succeed without making any changes.
+
+ In order to get access to this endpoint, contact our support team.
+
+ 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 phone_number:
+ raise ValueError(f"Expected a non-empty value for `phone_number` but received {phone_number!r}")
+ return self._delete(
+ path_template(
+ "/v2/verification/management/phone-numbers/sandbox/{phone_number}", phone_number=phone_number
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SandboxDeletePhoneNumberResponse,
+ )
+
+ def list_phone_numbers(
+ self,
+ *,
+ # 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,
+ ) -> SandboxListPhoneNumbersResponse:
+ """Retrieve the list of sandbox phone numbers for the account.
+
+ Sandbox numbers are
+ test numbers that bypass the real verification flow and return a fixed attempt
+ code.
+
+ In order to get access to this endpoint, contact our support team.
+ """
+ return self._get(
+ "/v2/verification/management/phone-numbers/sandbox",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SandboxListPhoneNumbersResponse,
+ )
+
+
+class AsyncSandboxResource(AsyncAPIResource):
+ """Verify phone numbers."""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncSandboxResourceWithRawResponse:
+ """
+ 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/prelude-so/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncSandboxResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncSandboxResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/prelude-so/python-sdk#with_streaming_response
+ """
+ return AsyncSandboxResourceWithStreamingResponse(self)
+
+ async def add_phone_number(
+ self,
+ *,
+ attempt_code: str,
+ phone_number: 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,
+ ) -> SandboxAddPhoneNumberResponse:
+ """
+ Register a phone number as a sandbox number and associate it with a fixed
+ attempt code. Subsequent verification attempts against this number will not
+ trigger a real SMS/call and will validate against the configured attempt code.
+
+ This operation is idempotent - re-adding the same phone number will overwrite
+ the existing attempt code.
+
+ In order to get access to this endpoint, contact our support team.
+
+ Args:
+ attempt_code: The fixed attempt code that will validate verification attempts for this phone
+ number.
+
+ phone_number: An E.164 formatted phone number to add to the sandbox list.
+
+ 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
+ """
+ return await self._put(
+ "/v2/verification/management/phone-numbers/sandbox",
+ body=await async_maybe_transform(
+ {
+ "attempt_code": attempt_code,
+ "phone_number": phone_number,
+ },
+ sandbox_add_phone_number_params.SandboxAddPhoneNumberParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SandboxAddPhoneNumberResponse,
+ )
+
+ async def delete_phone_number(
+ self,
+ phone_number: 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,
+ ) -> SandboxDeletePhoneNumberResponse:
+ """
+ Remove a phone number from the sandbox list.
+
+ This operation is idempotent - deleting a phone number that is not in the
+ sandbox list will succeed without making any changes.
+
+ In order to get access to this endpoint, contact our support team.
+
+ 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 phone_number:
+ raise ValueError(f"Expected a non-empty value for `phone_number` but received {phone_number!r}")
+ return await self._delete(
+ path_template(
+ "/v2/verification/management/phone-numbers/sandbox/{phone_number}", phone_number=phone_number
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SandboxDeletePhoneNumberResponse,
+ )
+
+ async def list_phone_numbers(
+ self,
+ *,
+ # 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,
+ ) -> SandboxListPhoneNumbersResponse:
+ """Retrieve the list of sandbox phone numbers for the account.
+
+ Sandbox numbers are
+ test numbers that bypass the real verification flow and return a fixed attempt
+ code.
+
+ In order to get access to this endpoint, contact our support team.
+ """
+ return await self._get(
+ "/v2/verification/management/phone-numbers/sandbox",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SandboxListPhoneNumbersResponse,
+ )
+
+
+class SandboxResourceWithRawResponse:
+ def __init__(self, sandbox: SandboxResource) -> None:
+ self._sandbox = sandbox
+
+ self.add_phone_number = to_raw_response_wrapper(
+ sandbox.add_phone_number,
+ )
+ self.delete_phone_number = to_raw_response_wrapper(
+ sandbox.delete_phone_number,
+ )
+ self.list_phone_numbers = to_raw_response_wrapper(
+ sandbox.list_phone_numbers,
+ )
+
+
+class AsyncSandboxResourceWithRawResponse:
+ def __init__(self, sandbox: AsyncSandboxResource) -> None:
+ self._sandbox = sandbox
+
+ self.add_phone_number = async_to_raw_response_wrapper(
+ sandbox.add_phone_number,
+ )
+ self.delete_phone_number = async_to_raw_response_wrapper(
+ sandbox.delete_phone_number,
+ )
+ self.list_phone_numbers = async_to_raw_response_wrapper(
+ sandbox.list_phone_numbers,
+ )
+
+
+class SandboxResourceWithStreamingResponse:
+ def __init__(self, sandbox: SandboxResource) -> None:
+ self._sandbox = sandbox
+
+ self.add_phone_number = to_streamed_response_wrapper(
+ sandbox.add_phone_number,
+ )
+ self.delete_phone_number = to_streamed_response_wrapper(
+ sandbox.delete_phone_number,
+ )
+ self.list_phone_numbers = to_streamed_response_wrapper(
+ sandbox.list_phone_numbers,
+ )
+
+
+class AsyncSandboxResourceWithStreamingResponse:
+ def __init__(self, sandbox: AsyncSandboxResource) -> None:
+ self._sandbox = sandbox
+
+ self.add_phone_number = async_to_streamed_response_wrapper(
+ sandbox.add_phone_number,
+ )
+ self.delete_phone_number = async_to_streamed_response_wrapper(
+ sandbox.delete_phone_number,
+ )
+ self.list_phone_numbers = async_to_streamed_response_wrapper(
+ sandbox.list_phone_numbers,
+ )
diff --git a/src/prelude_python_sdk/resources/verification_management.py b/src/prelude_python_sdk/resources/verification_management/verification_management.py
similarity index 90%
rename from src/prelude_python_sdk/resources/verification_management.py
rename to src/prelude_python_sdk/resources/verification_management/verification_management.py
index 244e7416..dfd3d49c 100644
--- a/src/prelude_python_sdk/resources/verification_management.py
+++ b/src/prelude_python_sdk/resources/verification_management/verification_management.py
@@ -6,27 +6,37 @@
import httpx
-from ..types import (
+from ...types import (
verification_management_set_phone_number_params,
verification_management_submit_sender_id_params,
verification_management_delete_phone_number_params,
)
-from .._types import Body, Query, Headers, NotGiven, not_given
-from .._utils import path_template, maybe_transform, async_maybe_transform
-from .._compat import cached_property
-from .._resource import SyncAPIResource, AsyncAPIResource
-from .._response import (
+from .sandbox import (
+ SandboxResource,
+ AsyncSandboxResource,
+ SandboxResourceWithRawResponse,
+ AsyncSandboxResourceWithRawResponse,
+ SandboxResourceWithStreamingResponse,
+ AsyncSandboxResourceWithStreamingResponse,
+)
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
to_raw_response_wrapper,
to_streamed_response_wrapper,
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
-from .._base_client import make_request_options
-from ..types.verification_management_list_sender_ids_response import VerificationManagementListSenderIDsResponse
-from ..types.verification_management_set_phone_number_response import VerificationManagementSetPhoneNumberResponse
-from ..types.verification_management_submit_sender_id_response import VerificationManagementSubmitSenderIDResponse
-from ..types.verification_management_list_phone_numbers_response import VerificationManagementListPhoneNumbersResponse
-from ..types.verification_management_delete_phone_number_response import VerificationManagementDeletePhoneNumberResponse
+from ..._base_client import make_request_options
+from ...types.verification_management_list_sender_ids_response import VerificationManagementListSenderIDsResponse
+from ...types.verification_management_set_phone_number_response import VerificationManagementSetPhoneNumberResponse
+from ...types.verification_management_submit_sender_id_response import VerificationManagementSubmitSenderIDResponse
+from ...types.verification_management_list_phone_numbers_response import VerificationManagementListPhoneNumbersResponse
+from ...types.verification_management_delete_phone_number_response import (
+ VerificationManagementDeletePhoneNumberResponse,
+)
__all__ = ["VerificationManagementResource", "AsyncVerificationManagementResource"]
@@ -34,6 +44,11 @@
class VerificationManagementResource(SyncAPIResource):
"""Verify phone numbers."""
+ @cached_property
+ def sandbox(self) -> SandboxResource:
+ """Verify phone numbers."""
+ return SandboxResource(self._client)
+
@cached_property
def with_raw_response(self) -> VerificationManagementResourceWithRawResponse:
"""
@@ -246,6 +261,11 @@ def submit_sender_id(
class AsyncVerificationManagementResource(AsyncAPIResource):
"""Verify phone numbers."""
+ @cached_property
+ def sandbox(self) -> AsyncSandboxResource:
+ """Verify phone numbers."""
+ return AsyncSandboxResource(self._client)
+
@cached_property
def with_raw_response(self) -> AsyncVerificationManagementResourceWithRawResponse:
"""
@@ -475,6 +495,11 @@ def __init__(self, verification_management: VerificationManagementResource) -> N
verification_management.submit_sender_id,
)
+ @cached_property
+ def sandbox(self) -> SandboxResourceWithRawResponse:
+ """Verify phone numbers."""
+ return SandboxResourceWithRawResponse(self._verification_management.sandbox)
+
class AsyncVerificationManagementResourceWithRawResponse:
def __init__(self, verification_management: AsyncVerificationManagementResource) -> None:
@@ -496,6 +521,11 @@ def __init__(self, verification_management: AsyncVerificationManagementResource)
verification_management.submit_sender_id,
)
+ @cached_property
+ def sandbox(self) -> AsyncSandboxResourceWithRawResponse:
+ """Verify phone numbers."""
+ return AsyncSandboxResourceWithRawResponse(self._verification_management.sandbox)
+
class VerificationManagementResourceWithStreamingResponse:
def __init__(self, verification_management: VerificationManagementResource) -> None:
@@ -517,6 +547,11 @@ def __init__(self, verification_management: VerificationManagementResource) -> N
verification_management.submit_sender_id,
)
+ @cached_property
+ def sandbox(self) -> SandboxResourceWithStreamingResponse:
+ """Verify phone numbers."""
+ return SandboxResourceWithStreamingResponse(self._verification_management.sandbox)
+
class AsyncVerificationManagementResourceWithStreamingResponse:
def __init__(self, verification_management: AsyncVerificationManagementResource) -> None:
@@ -537,3 +572,8 @@ def __init__(self, verification_management: AsyncVerificationManagementResource)
self.submit_sender_id = async_to_streamed_response_wrapper(
verification_management.submit_sender_id,
)
+
+ @cached_property
+ def sandbox(self) -> AsyncSandboxResourceWithStreamingResponse:
+ """Verify phone numbers."""
+ return AsyncSandboxResourceWithStreamingResponse(self._verification_management.sandbox)
diff --git a/src/prelude_python_sdk/resources/watch.py b/src/prelude_python_sdk/resources/watch.py
index 9e126fc3..a4e70adc 100644
--- a/src/prelude_python_sdk/resources/watch.py
+++ b/src/prelude_python_sdk/resources/watch.py
@@ -2,11 +2,16 @@
from __future__ import annotations
-from typing import Iterable
+from typing import Dict, Iterable
import httpx
-from ..types import watch_predict_params, watch_send_events_params, watch_send_feedbacks_params
+from ..types import (
+ watch_predict_params,
+ watch_evaluate_params,
+ watch_send_events_params,
+ watch_send_feedbacks_params,
+)
from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from .._utils import maybe_transform, async_maybe_transform
from .._compat import cached_property
@@ -18,7 +23,10 @@
async_to_streamed_response_wrapper,
)
from .._base_client import make_request_options
+from ..types.shared_params.target import Target
+from ..types.shared_params.signals import Signals
from ..types.watch_predict_response import WatchPredictResponse
+from ..types.watch_evaluate_response import WatchEvaluateResponse
from ..types.watch_send_events_response import WatchSendEventsResponse
from ..types.watch_send_feedbacks_response import WatchSendFeedbacksResponse
@@ -47,13 +55,89 @@ def with_streaming_response(self) -> WatchResourceWithStreamingResponse:
"""
return WatchResourceWithStreamingResponse(self)
+ def evaluate(
+ self,
+ *,
+ flow_id: str,
+ target: Target,
+ attributes: Dict[str, str] | Omit = omit,
+ dispatch_id: str | Omit = omit,
+ signals: Signals | 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,
+ ) -> WatchEvaluateResponse:
+ """
+ **Beta.** The request and response shapes may still change, and flows and
+ recipes are configured by Prelude on your behalf for now. Talk to us before you
+ build against it.
+
+ Score a target against the rules configured for one moment in your product —
+ signup, checkout, password reset. The flow selects which recipes run; each
+ recipe scores its rules against a threshold and returns its own verdict, and the
+ evaluation answers with the most severe verdict and action across them. Where
+ Predict returns a single model-derived outcome, Eval returns the full breakdown,
+ so you can see which rules fired and which could not run. Scoring-only — it does
+ not update counters by itself.
+
+ Args:
+ flow_id: The flow to evaluate. A flow names the moment you are guarding and selects the
+ recipes that run.
+
+ target: The identifier to score — a phone number or email address.
+
+ attributes: Values for the attributes the flow's recipes declare, keyed without the `attr.`
+ namespace a rule uses to reference them.
+
+ An attribute a recipe declares and this request omits is treated as missing
+ evidence, not as an empty value: the rules reading it report `NOT_EVALUATED`
+ rather than being scored as though the condition were false. A key no recipe in
+ the flow declares is ignored rather than rejected, so one payload can serve
+ flows that read different attributes.
+
+ dispatch_id: The identifier of the dispatch that came from the front-end SDK. Signals it
+ carries fill in anything the request did not state; the request wins where both
+ supply a value.
+
+ signals: The signals used for anti-fraud. For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
+
+ 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
+ """
+ return self._post(
+ "/v2/watch/eval",
+ body=maybe_transform(
+ {
+ "flow_id": flow_id,
+ "target": target,
+ "attributes": attributes,
+ "dispatch_id": dispatch_id,
+ "signals": signals,
+ },
+ watch_evaluate_params.WatchEvaluateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WatchEvaluateResponse,
+ )
+
def predict(
self,
*,
- target: watch_predict_params.Target,
+ target: Target,
dispatch_id: str | Omit = omit,
metadata: watch_predict_params.Metadata | Omit = omit,
- signals: watch_predict_params.Signals | Omit = omit,
+ signals: Signals | 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,
@@ -208,13 +292,89 @@ def with_streaming_response(self) -> AsyncWatchResourceWithStreamingResponse:
"""
return AsyncWatchResourceWithStreamingResponse(self)
+ async def evaluate(
+ self,
+ *,
+ flow_id: str,
+ target: Target,
+ attributes: Dict[str, str] | Omit = omit,
+ dispatch_id: str | Omit = omit,
+ signals: Signals | 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,
+ ) -> WatchEvaluateResponse:
+ """
+ **Beta.** The request and response shapes may still change, and flows and
+ recipes are configured by Prelude on your behalf for now. Talk to us before you
+ build against it.
+
+ Score a target against the rules configured for one moment in your product —
+ signup, checkout, password reset. The flow selects which recipes run; each
+ recipe scores its rules against a threshold and returns its own verdict, and the
+ evaluation answers with the most severe verdict and action across them. Where
+ Predict returns a single model-derived outcome, Eval returns the full breakdown,
+ so you can see which rules fired and which could not run. Scoring-only — it does
+ not update counters by itself.
+
+ Args:
+ flow_id: The flow to evaluate. A flow names the moment you are guarding and selects the
+ recipes that run.
+
+ target: The identifier to score — a phone number or email address.
+
+ attributes: Values for the attributes the flow's recipes declare, keyed without the `attr.`
+ namespace a rule uses to reference them.
+
+ An attribute a recipe declares and this request omits is treated as missing
+ evidence, not as an empty value: the rules reading it report `NOT_EVALUATED`
+ rather than being scored as though the condition were false. A key no recipe in
+ the flow declares is ignored rather than rejected, so one payload can serve
+ flows that read different attributes.
+
+ dispatch_id: The identifier of the dispatch that came from the front-end SDK. Signals it
+ carries fill in anything the request did not state; the request wins where both
+ supply a value.
+
+ signals: The signals used for anti-fraud. For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
+
+ 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
+ """
+ return await self._post(
+ "/v2/watch/eval",
+ body=await async_maybe_transform(
+ {
+ "flow_id": flow_id,
+ "target": target,
+ "attributes": attributes,
+ "dispatch_id": dispatch_id,
+ "signals": signals,
+ },
+ watch_evaluate_params.WatchEvaluateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WatchEvaluateResponse,
+ )
+
async def predict(
self,
*,
- target: watch_predict_params.Target,
+ target: Target,
dispatch_id: str | Omit = omit,
metadata: watch_predict_params.Metadata | Omit = omit,
- signals: watch_predict_params.Signals | Omit = omit,
+ signals: Signals | 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,
@@ -353,6 +513,9 @@ class WatchResourceWithRawResponse:
def __init__(self, watch: WatchResource) -> None:
self._watch = watch
+ self.evaluate = to_raw_response_wrapper(
+ watch.evaluate,
+ )
self.predict = to_raw_response_wrapper(
watch.predict,
)
@@ -368,6 +531,9 @@ class AsyncWatchResourceWithRawResponse:
def __init__(self, watch: AsyncWatchResource) -> None:
self._watch = watch
+ self.evaluate = async_to_raw_response_wrapper(
+ watch.evaluate,
+ )
self.predict = async_to_raw_response_wrapper(
watch.predict,
)
@@ -383,6 +549,9 @@ class WatchResourceWithStreamingResponse:
def __init__(self, watch: WatchResource) -> None:
self._watch = watch
+ self.evaluate = to_streamed_response_wrapper(
+ watch.evaluate,
+ )
self.predict = to_streamed_response_wrapper(
watch.predict,
)
@@ -398,6 +567,9 @@ class AsyncWatchResourceWithStreamingResponse:
def __init__(self, watch: AsyncWatchResource) -> None:
self._watch = watch
+ self.evaluate = async_to_streamed_response_wrapper(
+ watch.evaluate,
+ )
self.predict = async_to_streamed_response_wrapper(
watch.predict,
)
diff --git a/src/prelude_python_sdk/types/__init__.py b/src/prelude_python_sdk/types/__init__.py
index f8619cb7..e3994fe5 100644
--- a/src/prelude_python_sdk/types/__init__.py
+++ b/src/prelude_python_sdk/types/__init__.py
@@ -2,12 +2,17 @@
from __future__ import annotations
+from .shared import Target as Target, Signals as Signals
from .notify_send_params import NotifySendParams as NotifySendParams
+from .notify_reply_params import NotifyReplyParams as NotifyReplyParams
from .lookup_lookup_params import LookupLookupParams as LookupLookupParams
from .notify_send_response import NotifySendResponse as NotifySendResponse
from .watch_predict_params import WatchPredictParams as WatchPredictParams
+from .notify_reply_response import NotifyReplyResponse as NotifyReplyResponse
+from .watch_evaluate_params import WatchEvaluateParams as WatchEvaluateParams
from .lookup_lookup_response import LookupLookupResponse as LookupLookupResponse
from .watch_predict_response import WatchPredictResponse as WatchPredictResponse
+from .watch_evaluate_response import WatchEvaluateResponse as WatchEvaluateResponse
from .notify_send_batch_params import NotifySendBatchParams as NotifySendBatchParams
from .watch_send_events_params import WatchSendEventsParams as WatchSendEventsParams
from .transactional_send_params import TransactionalSendParams as TransactionalSendParams
diff --git a/src/prelude_python_sdk/types/intel/__init__.py b/src/prelude_python_sdk/types/intel/__init__.py
new file mode 100644
index 00000000..7762c407
--- /dev/null
+++ b/src/prelude_python_sdk/types/intel/__init__.py
@@ -0,0 +1,6 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .kyc_match_params import KYCMatchParams as KYCMatchParams
+from .kyc_match_response import KYCMatchResponse as KYCMatchResponse
diff --git a/src/prelude_python_sdk/types/intel/kyc_match_params.py b/src/prelude_python_sdk/types/intel/kyc_match_params.py
new file mode 100644
index 00000000..3f7ab0d3
--- /dev/null
+++ b/src/prelude_python_sdk/types/intel/kyc_match_params.py
@@ -0,0 +1,40 @@
+# 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__ = ["KYCMatchParams"]
+
+
+class KYCMatchParams(TypedDict, total=False):
+ address: str
+ """The street address."""
+
+ birthdate: Annotated[Union[str, date], PropertyInfo(format="iso8601")]
+ """The date of birth in ISO 8601 (`YYYY-MM-DD`) format. Compared exactly."""
+
+ country: str
+ """The ISO 3166-1 alpha-2 country code. Compared exactly."""
+
+ email: str
+ """The email address."""
+
+ family_name: str
+ """The end-user's family (last) name."""
+
+ given_name: str
+ """The end-user's given (first) name."""
+
+ locality: str
+ """The locality (city)."""
+
+ postal_code: str
+ """The postal code. Compared exactly."""
+
+ region: str
+ """The region, state, or province."""
diff --git a/src/prelude_python_sdk/types/intel/kyc_match_response.py b/src/prelude_python_sdk/types/intel/kyc_match_response.py
new file mode 100644
index 00000000..e67148c8
--- /dev/null
+++ b/src/prelude_python_sdk/types/intel/kyc_match_response.py
@@ -0,0 +1,84 @@
+# 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__ = ["KYCMatchResponse"]
+
+
+class KYCMatchResponse(BaseModel):
+ """The per-attribute match result.
+
+ Each `_match` field is one of `true`, `false`, or `not_available` (the operator could not answer for that attribute). Fuzzy attributes additionally return a `_match_score` (0-99 similarity) when they do not match exactly; the score is omitted on a match or when `not_available`.
+ """
+
+ address_match: Optional[Literal["true", "false", "not_available"]] = None
+ """Whether the street address matched the operator's record."""
+
+ address_match_score: Optional[int] = None
+ """Similarity score (0-99) for the address. Returned only on a non-match."""
+
+ birthdate_match: Optional[Literal["true", "false", "not_available"]] = None
+ """Whether the date of birth matched the operator's record.
+
+ Compared exactly; never scored.
+ """
+
+ country_code: Optional[str] = None
+ """The country code of the phone number."""
+
+ country_match: Optional[Literal["true", "false", "not_available"]] = None
+ """Whether the country matched the operator's record.
+
+ Compared exactly; never scored.
+ """
+
+ email_match: Optional[Literal["true", "false", "not_available"]] = None
+ """Whether the email address matched the operator's record."""
+
+ email_match_score: Optional[int] = None
+ """Similarity score (0-99) for the email. Returned only on a non-match."""
+
+ family_name_match: Optional[Literal["true", "false", "not_available"]] = None
+ """Whether the family name matched the operator's record."""
+
+ family_name_match_score: Optional[int] = None
+ """Similarity score (0-99) for the family name. Returned only on a non-match."""
+
+ given_name_match: Optional[Literal["true", "false", "not_available"]] = None
+ """Whether the given name matched the operator's record."""
+
+ given_name_match_score: Optional[int] = None
+ """Similarity score (0-99) for the given name. Returned only on a non-match."""
+
+ locality_match: Optional[Literal["true", "false", "not_available"]] = None
+ """Whether the locality matched the operator's record."""
+
+ locality_match_score: Optional[int] = None
+ """Similarity score (0-99) for the locality. Returned only on a non-match."""
+
+ operator: Optional[str] = None
+ """The mobile operator that answered the match."""
+
+ phone_number: Optional[str] = None
+ """The phone number that was matched, in E.164 format."""
+
+ postal_code_match: Optional[Literal["true", "false", "not_available"]] = None
+ """Whether the postal code matched the operator's record.
+
+ Compared exactly; never scored.
+ """
+
+ region_match: Optional[Literal["true", "false", "not_available"]] = None
+ """Whether the region matched the operator's record."""
+
+ region_match_score: Optional[int] = None
+ """Similarity score (0-99) for the region. Returned only on a non-match."""
+
+ request_id: Optional[str] = None
+ """A string that identifies this specific request.
+
+ Report it back to us to help us diagnose your issues.
+ """
diff --git a/src/prelude_python_sdk/types/notify_reply_params.py b/src/prelude_python_sdk/types/notify_reply_params.py
new file mode 100644
index 00000000..9383016f
--- /dev/null
+++ b/src/prelude_python_sdk/types/notify_reply_params.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["NotifyReplyParams"]
+
+
+class NotifyReplyParams(TypedDict, total=False):
+ reply_to: Required[str]
+ """The inbound message ID (prefixed with `im_`) to reply to.
+
+ This ID is provided in the `inbound.message.received` webhook event.
+ """
+
+ text: Required[str]
+ """The reply message body sent as a free-form WhatsApp text."""
+
+ to: Required[str]
+ """The recipient's phone number in E.164 format.
+
+ Must match the phone number that sent the original inbound message.
+ """
+
+ callback_url: str
+ """The URL where webhooks will be sent for delivery events of this reply."""
+
+ correlation_id: str
+ """A user-defined identifier to correlate this reply with your internal systems.
+
+ It is returned in the response and any webhook events that refer to this
+ message.
+ """
diff --git a/src/prelude_python_sdk/types/notify_reply_response.py b/src/prelude_python_sdk/types/notify_reply_response.py
new file mode 100644
index 00000000..79e34865
--- /dev/null
+++ b/src/prelude_python_sdk/types/notify_reply_response.py
@@ -0,0 +1,31 @@
+# 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
+
+__all__ = ["NotifyReplyResponse"]
+
+
+class NotifyReplyResponse(BaseModel):
+ id: str
+ """The reply message identifier."""
+
+ created_at: datetime
+ """The reply creation date in RFC3339 format."""
+
+ reply_to: str
+ """The inbound message ID this reply was sent in response to."""
+
+ text: str
+ """The reply message body that was sent."""
+
+ to: str
+ """The recipient's phone number in E.164 format."""
+
+ callback_url: Optional[str] = None
+ """The callback URL where webhooks will be sent."""
+
+ correlation_id: Optional[str] = None
+ """The user-defined correlation identifier echoed back from the request."""
diff --git a/src/prelude_python_sdk/types/shared/__init__.py b/src/prelude_python_sdk/types/shared/__init__.py
new file mode 100644
index 00000000..abf0e8bb
--- /dev/null
+++ b/src/prelude_python_sdk/types/shared/__init__.py
@@ -0,0 +1,4 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .target import Target as Target
+from .signals import Signals as Signals
diff --git a/src/prelude_python_sdk/types/shared/signals.py b/src/prelude_python_sdk/types/shared/signals.py
new file mode 100644
index 00000000..73864312
--- /dev/null
+++ b/src/prelude_python_sdk/types/shared/signals.py
@@ -0,0 +1,75 @@
+# 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__ = ["Signals"]
+
+
+class Signals(BaseModel):
+ """The signals used for anti-fraud.
+
+ For more details, refer to [Signals](/verify/v2/documentation/prevent-fraud#signals).
+ """
+
+ app_version: Optional[str] = None
+ """The version of your application."""
+
+ device_id: Optional[str] = None
+ """A unique ID for the user's device.
+
+ You should ensure that each user device has a unique `device_id` value. Ideally,
+ for Android, this corresponds to the `ANDROID_ID` and for iOS, this corresponds
+ to the `identifierForVendor`.
+ """
+
+ device_model: Optional[str] = None
+ """The model of the user's device."""
+
+ device_platform: Optional[Literal["android", "ios", "ipados", "tvos", "web"]] = None
+ """The type of the user's device."""
+
+ existing_user: Optional[bool] = None
+ """
+ Whether the end-user already exists in your system, for example an existing
+ account signing in again rather than a first-time signup. Unlike
+ `is_trusted_user`, this signal does not bypass fraud checks; it is taken into
+ account as one additional anti-fraud signal. For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
+ """
+
+ ip: Optional[str] = None
+ """The public IP v4 or v6 address of the end-user's device.
+
+ You should collect this from your backend. If your backend is behind a proxy,
+ use the `X-Forwarded-For`, `Forwarded`, `True-Client-IP`, `CF-Connecting-IP` or
+ an equivalent header to get the actual public IP of the end-user's device.
+ """
+
+ is_trusted_user: Optional[bool] = None
+ """
+ This signal should indicate a higher level of trust, explicitly stating that the
+ user is genuine. Contact us to discuss your use case. For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
+ """
+
+ ja4_fingerprint: Optional[str] = None
+ """The JA4 fingerprint observed for the end-user's connection.
+
+ Prelude will infer it automatically when you use our Frontend SDKs (which use
+ Prelude's edge network), but you can also forward the value if you terminate TLS
+ yourself.
+ """
+
+ os_version: Optional[str] = None
+ """The version of the user's device operating system."""
+
+ user_agent: Optional[str] = None
+ """The user agent of the user's device.
+
+ If the individual fields (os_version, device_platform, device_model) are
+ provided, we will prioritize those values instead of parsing them from the user
+ agent string.
+ """
diff --git a/src/prelude_python_sdk/types/shared/target.py b/src/prelude_python_sdk/types/shared/target.py
new file mode 100644
index 00000000..34d6035e
--- /dev/null
+++ b/src/prelude_python_sdk/types/shared/target.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["Target"]
+
+
+class Target(BaseModel):
+ """The operation target. Either a phone number or an email address."""
+
+ type: Literal["phone_number", "email_address"]
+ """The type of the target. Either "phone_number" or "email_address"."""
+
+ value: str
+ """An E.164 formatted phone number or an email address."""
diff --git a/src/prelude_python_sdk/types/shared_params/__init__.py b/src/prelude_python_sdk/types/shared_params/__init__.py
new file mode 100644
index 00000000..abf0e8bb
--- /dev/null
+++ b/src/prelude_python_sdk/types/shared_params/__init__.py
@@ -0,0 +1,4 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .target import Target as Target
+from .signals import Signals as Signals
diff --git a/src/prelude_python_sdk/types/shared_params/signals.py b/src/prelude_python_sdk/types/shared_params/signals.py
new file mode 100644
index 00000000..19288201
--- /dev/null
+++ b/src/prelude_python_sdk/types/shared_params/signals.py
@@ -0,0 +1,74 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["Signals"]
+
+
+class Signals(TypedDict, total=False):
+ """The signals used for anti-fraud.
+
+ For more details, refer to [Signals](/verify/v2/documentation/prevent-fraud#signals).
+ """
+
+ app_version: str
+ """The version of your application."""
+
+ device_id: str
+ """A unique ID for the user's device.
+
+ You should ensure that each user device has a unique `device_id` value. Ideally,
+ for Android, this corresponds to the `ANDROID_ID` and for iOS, this corresponds
+ to the `identifierForVendor`.
+ """
+
+ device_model: str
+ """The model of the user's device."""
+
+ device_platform: Literal["android", "ios", "ipados", "tvos", "web"]
+ """The type of the user's device."""
+
+ existing_user: bool
+ """
+ Whether the end-user already exists in your system, for example an existing
+ account signing in again rather than a first-time signup. Unlike
+ `is_trusted_user`, this signal does not bypass fraud checks; it is taken into
+ account as one additional anti-fraud signal. For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
+ """
+
+ ip: str
+ """The public IP v4 or v6 address of the end-user's device.
+
+ You should collect this from your backend. If your backend is behind a proxy,
+ use the `X-Forwarded-For`, `Forwarded`, `True-Client-IP`, `CF-Connecting-IP` or
+ an equivalent header to get the actual public IP of the end-user's device.
+ """
+
+ is_trusted_user: bool
+ """
+ This signal should indicate a higher level of trust, explicitly stating that the
+ user is genuine. Contact us to discuss your use case. For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
+ """
+
+ ja4_fingerprint: str
+ """The JA4 fingerprint observed for the end-user's connection.
+
+ Prelude will infer it automatically when you use our Frontend SDKs (which use
+ Prelude's edge network), but you can also forward the value if you terminate TLS
+ yourself.
+ """
+
+ os_version: str
+ """The version of the user's device operating system."""
+
+ user_agent: str
+ """The user agent of the user's device.
+
+ If the individual fields (os_version, device_platform, device_model) are
+ provided, we will prioritize those values instead of parsing them from the user
+ agent string.
+ """
diff --git a/src/prelude_python_sdk/types/shared_params/target.py b/src/prelude_python_sdk/types/shared_params/target.py
new file mode 100644
index 00000000..f5ca9915
--- /dev/null
+++ b/src/prelude_python_sdk/types/shared_params/target.py
@@ -0,0 +1,17 @@
+# 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__ = ["Target"]
+
+
+class Target(TypedDict, total=False):
+ """The operation target. Either a phone number or an email address."""
+
+ type: Required[Literal["phone_number", "email_address"]]
+ """The type of the target. Either "phone_number" or "email_address"."""
+
+ value: Required[str]
+ """An E.164 formatted phone number or an email address."""
diff --git a/src/prelude_python_sdk/types/verification/__init__.py b/src/prelude_python_sdk/types/verification/__init__.py
new file mode 100644
index 00000000..f8ee8b14
--- /dev/null
+++ b/src/prelude_python_sdk/types/verification/__init__.py
@@ -0,0 +1,3 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
diff --git a/src/prelude_python_sdk/types/verification/phone/__init__.py b/src/prelude_python_sdk/types/verification/phone/__init__.py
new file mode 100644
index 00000000..ea66eb09
--- /dev/null
+++ b/src/prelude_python_sdk/types/verification/phone/__init__.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .history_list_params import HistoryListParams as HistoryListParams
+from .history_list_response import HistoryListResponse as HistoryListResponse
+from .phone_verification_money import PhoneVerificationMoney as PhoneVerificationMoney
+from .history_retrieve_response import HistoryRetrieveResponse as HistoryRetrieveResponse
+from .phone_verification_carrier import PhoneVerificationCarrier as PhoneVerificationCarrier
+from .phone_verification_psd2_transaction import PhoneVerificationPsd2Transaction as PhoneVerificationPsd2Transaction
diff --git a/src/prelude_python_sdk/types/verification/phone/history_list_params.py b/src/prelude_python_sdk/types/verification/phone/history_list_params.py
new file mode 100644
index 00000000..87aa6b9e
--- /dev/null
+++ b/src/prelude_python_sdk/types/verification/phone/history_list_params.py
@@ -0,0 +1,85 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Union
+from datetime import datetime
+from typing_extensions import Literal, Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["HistoryListParams"]
+
+
+class HistoryListParams(TypedDict, total=False):
+ channels: List[Literal["sms", "rcs", "whatsapp", "viber", "zalo", "telegram", "voice", "silent"]]
+ """Only verifications that could use one of these channels.
+
+ Repeat the parameter for several values.
+ """
+
+ cursor: str
+ """Pagination cursor from the previous response."""
+
+ device_platform: Literal["android", "ios", "ipados", "tvos", "web"]
+ """Only verifications created from this device platform."""
+
+ from_: Annotated[Union[str, datetime], PropertyInfo(alias="from", format="iso8601")]
+ """Only verifications created at or after this RFC 3339 timestamp.
+
+ Goes with `to`, at most 6 months apart. Without them the whole history is
+ searched.
+ """
+
+ limit: int
+ """Maximum number of verifications to return per page."""
+
+ max_attempts: int
+ """Only verifications that sent at most this many messages.
+
+ `0` keeps the verifications that never sent one.
+ """
+
+ min_attempts: int
+ """Only verifications that sent at least this many messages."""
+
+ phone_number: str
+ """Only verifications targeting this E.164 phone number.
+
+ The leading `+` may be omitted.
+ """
+
+ region: str
+ """
+ Only verifications of phone numbers from this region, as an ISO 3166-1 alpha-2
+ code.
+ """
+
+ status: Literal[
+ "converted",
+ "not_converted",
+ "pending_check",
+ "sent",
+ "challenged",
+ "suspected_fraud",
+ "in_blocklist",
+ "invalid_line",
+ "invalid_number",
+ "rate_limited",
+ "expired_signals",
+ "shadowed",
+ ]
+ """Only verifications in this status. `pending_check` cannot be filtered on."""
+
+ template_id: str
+ """
+ Only verifications sent with this template, as returned in `template_id` by
+ [Get a phone verification](/verify/v2/api-reference/history/get-a-phone-verification).
+ Built-in templates (`prelude:*`) cannot be filtered on.
+ """
+
+ to: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
+ """Only verifications created at or before this RFC 3339 timestamp.
+
+ Goes with `from`.
+ """
diff --git a/src/prelude_python_sdk/types/verification/phone/history_list_response.py b/src/prelude_python_sdk/types/verification/phone/history_list_response.py
new file mode 100644
index 00000000..83b6d075
--- /dev/null
+++ b/src/prelude_python_sdk/types/verification/phone/history_list_response.py
@@ -0,0 +1,113 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ...._models import BaseModel
+from .phone_verification_money import PhoneVerificationMoney
+
+__all__ = ["HistoryListResponse", "Verification", "VerificationChannel"]
+
+
+class VerificationChannel(BaseModel):
+ channel: Literal["sms", "rcs", "whatsapp", "viber", "zalo", "telegram", "voice", "silent"]
+
+ converted: bool
+ """Whether the end user submitted a valid code received through this channel."""
+
+
+class Verification(BaseModel):
+ """One entry of the verification history.
+
+ [Get a phone verification](/verify/v2/api-reference/history/get-a-phone-verification) returns the full record.
+ """
+
+ id: str
+ """The verification identifier."""
+
+ channels: List[VerificationChannel]
+ """
+ The channels the verification could use, and which one the end user converted
+ through. Empty when the verification used only channels this API does not list.
+ """
+
+ created_at: datetime
+
+ delivered: bool
+ """Whether at least one message was reported delivered."""
+
+ phone_number: str
+ """The E.164 phone number the verification targeted."""
+
+ status: Literal[
+ "converted",
+ "not_converted",
+ "pending_check",
+ "sent",
+ "challenged",
+ "suspected_fraud",
+ "in_blocklist",
+ "invalid_line",
+ "invalid_number",
+ "rate_limited",
+ "expired_signals",
+ "shadowed",
+ ]
+ """The outcome of the verification.
+
+ - `converted` - The end user submitted a valid code.
+ - `not_converted` - The verification expired without a valid code.
+ - `pending_check` - A code was delivered and Prelude is still waiting for a
+ check.
+ - `sent` - A code was sent and the verification window is still open.
+ - `challenged` - The verification was restricted to non-SMS and non-voice
+ channels.
+ - `suspected_fraud` - The anti-fraud system blocked the verification.
+ - `in_blocklist` - The phone number is on the configured block list.
+ - `invalid_line` - The phone number is not a valid line type.
+ - `invalid_number` - The phone number is not a valid number.
+ - `rate_limited` - The verification was refused by a rate limit.
+ - `expired_signals` - The SDK signals were collected too long before the
+ request.
+ - `shadowed` - The anti-fraud system flagged the verification without blocking
+ it.
+ """
+
+ attempts: Optional[int] = None
+ """Number of messages sent for the verification, `0` when none was.
+
+ Absent for sandboxed phone numbers.
+ """
+
+ converted_at: Optional[datetime] = None
+ """When the end user submitted a valid code.
+
+ Absent unless the verification converted.
+ """
+
+ cost: Optional[PhoneVerificationMoney] = None
+ """Total cost of the verification. Absent when nothing was billed."""
+
+ device_platform: Optional[Literal["android", "ios", "ipados", "tvos", "web"]] = None
+ """Platform of the end-user device, when known."""
+
+ phone_number_condition: Optional[Literal["allow_listed", "block_listed", "sandboxed"]] = None
+ """
+ Whether the phone number was allow-listed, block-listed, or sandboxed at
+ verification time.
+ """
+
+ signals_hash_status: Optional[Literal["valid", "invalid"]] = None
+ """Whether the SDK signals integrity check passed."""
+
+
+class HistoryListResponse(BaseModel):
+ verifications: List[Verification]
+ """The page of verifications, most recent first."""
+
+ next_cursor: Optional[str] = None
+ """Pagination cursor for the next page of results.
+
+ Omitted if there are no more pages.
+ """
diff --git a/src/prelude_python_sdk/types/verification/phone/history_retrieve_response.py b/src/prelude_python_sdk/types/verification/phone/history_retrieve_response.py
new file mode 100644
index 00000000..e2b8de1f
--- /dev/null
+++ b/src/prelude_python_sdk/types/verification/phone/history_retrieve_response.py
@@ -0,0 +1,333 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ...._models import BaseModel
+from .phone_verification_money import PhoneVerificationMoney
+from .phone_verification_carrier import PhoneVerificationCarrier
+from .phone_verification_psd2_transaction import PhoneVerificationPsd2Transaction
+
+__all__ = [
+ "HistoryRetrieveResponse",
+ "Lifecycle",
+ "LifecycleEvent",
+ "LifecycleEventAttempt",
+ "LifecycleEventAttemptDeliveryEvent",
+ "LifecycleEventCheck",
+ "LifecycleEventCheckPsd2Info",
+ "LifecycleEventCreate",
+ "LifecycleEventSignals",
+ "Signals",
+]
+
+
+class LifecycleEventAttemptDeliveryEvent(BaseModel):
+ received_at: datetime
+
+ status: Literal[
+ "unknown",
+ "submitted",
+ "in_transit",
+ "delivered",
+ "undeliverable",
+ "expired",
+ "read",
+ "silent_started",
+ "silent_verified",
+ "silent_mismatch",
+ ]
+ """The state this event reported.
+
+ It is finer-grained than the attempt's `delivery_status` and includes the states
+ a silent verification goes through.
+ """
+
+
+class LifecycleEventAttempt(BaseModel):
+ """One message sent for this verification."""
+
+ id: str
+
+ created_at: datetime
+
+ carrier: Optional[PhoneVerificationCarrier] = None
+ """The end user's mobile network."""
+
+ channel: Optional[Literal["sms", "rcs", "whatsapp", "viber", "zalo", "telegram", "voice", "silent"]] = None
+
+ content: Optional[str] = None
+ """Message body.
+
+ While the verification can still be completed, the code inside it is masked
+ rather than removed.
+ """
+
+ cost: Optional[PhoneVerificationMoney] = None
+
+ delivery_events: Optional[List[LifecycleEventAttemptDeliveryEvent]] = None
+
+ delivery_status: Optional[Literal["unknown", "in_transit", "delivered", "undeliverable", "read"]] = None
+
+ preferred_channel: Optional[Literal["sms", "rcs", "whatsapp", "viber", "zalo", "telegram", "voice", "silent"]] = (
+ None
+ )
+ """Channel you asked for, when it differs from the one used."""
+
+ status: Optional[Literal["succeeded", "failed"]] = None
+
+ trigger: Optional[Literal["initial", "auto_retry", "user_retry"]] = None
+ """What caused the attempt."""
+
+
+class LifecycleEventCheckPsd2Info(BaseModel):
+ """Present on checks against a `prelude:psd2` code."""
+
+ expected_transaction: Optional[PhoneVerificationPsd2Transaction] = None
+ """The transaction submitted when the code was issued."""
+
+ received_transaction: Optional[PhoneVerificationPsd2Transaction] = None
+ """The transaction submitted with this check.
+
+ Differs from `expected_transaction` when `status_detail` is
+ `transaction_mismatch`.
+ """
+
+
+class LifecycleEventCheck(BaseModel):
+ """One code submission for this verification."""
+
+ created_at: datetime
+
+ is_valid: bool
+
+ channel: Optional[Literal["sms", "rcs", "whatsapp", "viber", "zalo", "telegram", "voice", "silent"]] = None
+
+ psd2_info: Optional[LifecycleEventCheckPsd2Info] = None
+ """Present on checks against a `prelude:psd2` code."""
+
+ status_detail: Optional[
+ Literal["expired_attempt", "expired_auth", "rate_limited", "transaction_missing", "transaction_mismatch"]
+ ] = None
+ """Why an invalid check failed, when known."""
+
+ value: Optional[str] = None
+ """The submitted code.
+
+ Absent while the verification can still be completed, so that a check in flight
+ cannot be read back through this endpoint, and absent on silent verification
+ checks, which carry no code.
+ """
+
+
+class LifecycleEventCreate(BaseModel):
+ created_at: datetime
+
+ cost: Optional[PhoneVerificationMoney] = None
+
+
+class LifecycleEventSignals(BaseModel):
+ received_at: datetime
+
+ expired_at: Optional[datetime] = None
+
+ status: Optional[Literal["valid", "invalid"]] = None
+
+
+class LifecycleEvent(BaseModel):
+ """One timeline entry. `type` names the single payload field that is set."""
+
+ type: Literal["create", "attempt", "check", "signals"]
+
+ attempt: Optional[LifecycleEventAttempt] = None
+ """One message sent for this verification."""
+
+ check: Optional[LifecycleEventCheck] = None
+ """One code submission for this verification."""
+
+ create: Optional[LifecycleEventCreate] = None
+
+ signals: Optional[LifecycleEventSignals] = None
+
+
+class Lifecycle(BaseModel):
+ """
+ Chronological timeline of the verification: creation, message attempts with delivery events, code checks and signals reception. Omitted when Prelude holds no timeline for the verification.
+ """
+
+ events: List[LifecycleEvent]
+
+ total_cost: Optional[PhoneVerificationMoney] = None
+
+ undeliverable_route_count: Optional[int] = None
+ """How many times the message was reported undeliverable by independent routes.
+
+ Above zero usually means the phone number is incorrect or the device
+ unreachable.
+ """
+
+
+class Signals(BaseModel):
+ """The anti-fraud signals you forwarded when creating the verification."""
+
+ is_trusted_user: bool
+ """Whether you flagged this end user as trusted when creating the verification.
+
+ Declared by you, not computed by Prelude.
+ """
+
+ device_id: Optional[str] = None
+ """End-user device identifier you forwarded."""
+
+ ja4_fingerprint: Optional[str] = None
+ """TLS fingerprint you forwarded."""
+
+ os_version: Optional[str] = None
+
+ user_agent: Optional[str] = None
+
+
+class HistoryRetrieveResponse(BaseModel):
+ """A verification and everything Prelude recorded about it."""
+
+ id: str
+ """The verification identifier."""
+
+ created_at: datetime
+
+ expires_at: datetime
+
+ phone_number: str
+ """The E.164 phone number the verification targeted."""
+
+ status: Literal[
+ "converted",
+ "not_converted",
+ "pending_check",
+ "sent",
+ "challenged",
+ "suspected_fraud",
+ "in_blocklist",
+ "invalid_line",
+ "invalid_number",
+ "rate_limited",
+ "expired_signals",
+ "shadowed",
+ ]
+ """The outcome of the verification.
+
+ - `converted` - The end user submitted a valid code.
+ - `not_converted` - The verification expired without a valid code.
+ - `pending_check` - A code was delivered and Prelude is still waiting for a
+ check.
+ - `sent` - A code was sent and the verification window is still open.
+ - `challenged` - The verification was restricted to non-SMS and non-voice
+ channels.
+ - `suspected_fraud` - The anti-fraud system blocked the verification.
+ - `in_blocklist` - The phone number is on the configured block list.
+ - `invalid_line` - The phone number is not a valid line type.
+ - `invalid_number` - The phone number is not a valid number.
+ - `rate_limited` - The verification was refused by a rate limit.
+ - `expired_signals` - The SDK signals were collected too long before the
+ request.
+ - `shadowed` - The anti-fraud system flagged the verification without blocking
+ it.
+ """
+
+ app_version: Optional[str] = None
+ """Version of your application, when known."""
+
+ block_reasons: Optional[
+ List[
+ Literal[
+ "behavioral_pattern",
+ "device_attribute",
+ "fraud_database",
+ "location_discrepancy",
+ "missing_signals",
+ "network_fingerprint",
+ "poor_conversion_history",
+ "prefix_concentration",
+ "repeated_number",
+ "suspected_request_tampering",
+ "suspicious_ip_address",
+ "temporary_phone_number",
+ ]
+ ]
+ ] = None
+ """Why the anti-fraud system blocked the verification. Empty unless it did.
+
+ - `behavioral_pattern` - The phone number past behavior during verification
+ flows exhibits suspicious patterns.
+ - `device_attribute` - The end-user device reported attributes associated with
+ fraud or emulation.
+ - `fraud_database` - The phone number appears in a fraud database.
+ - `location_discrepancy` - The phone number region and the observed location
+ disagree.
+ - `missing_signals` - The verification expected Prelude SDK signals and none
+ arrived.
+ - `network_fingerprint` - The network fingerprint matches known fraudulent
+ traffic.
+ - `poor_conversion_history` - The phone number rarely completes the
+ verifications it starts.
+ - `prefix_concentration` - The phone number is part of a range known to be
+ associated with suspicious activity patterns.
+ - `repeated_number` - The phone number was used far more often than normal
+ traffic would explain.
+ - `suspected_request_tampering` - The SDK signals were altered or expired
+ between collection and use.
+ - `suspicious_ip_address` - The originating IP address is associated with
+ suspicious activity.
+ - `temporary_phone_number` - The phone number is known to be a temporary or
+ disposable number.
+ """
+
+ carrier: Optional[PhoneVerificationCarrier] = None
+ """The end user's mobile network."""
+
+ correlation_id: Optional[str] = None
+ """The correlation identifier you supplied when creating the verification."""
+
+ device_model: Optional[str] = None
+ """Model of the end-user device, when known."""
+
+ device_platform: Optional[Literal["android", "ios", "ipados", "tvos", "web"]] = None
+ """Platform of the end-user device, when known."""
+
+ ip_address: Optional[str] = None
+ """IP address the verification was created from."""
+
+ ip_address_region: Optional[str] = None
+ """ISO 3166-1 alpha-2 region of the caller's IP address."""
+
+ ip_distance_meters: Optional[int] = None
+ """Distance between the phone number region and the IP location."""
+
+ lifecycle: Optional[Lifecycle] = None
+ """
+ Chronological timeline of the verification: creation, message attempts with
+ delivery events, code checks and signals reception. Omitted when Prelude holds
+ no timeline for the verification.
+ """
+
+ phone_number_condition: Optional[Literal["allow_listed", "block_listed", "sandboxed"]] = None
+ """
+ Whether the phone number was allow-listed, block-listed, or sandboxed at
+ verification time.
+ """
+
+ phone_number_current_condition: Optional[Literal["allow_listed", "block_listed", "sandboxed"]] = None
+ """Whether the phone number is currently allow-listed, block-listed, or sandboxed."""
+
+ phone_number_region: Optional[str] = None
+ """ISO 3166-1 alpha-2 region of the phone number."""
+
+ signals: Optional[Signals] = None
+ """The anti-fraud signals you forwarded when creating the verification."""
+
+ signals_hash_status: Optional[Literal["valid", "invalid"]] = None
+ """Whether the SDK signals integrity check passed."""
+
+ template_id: Optional[str] = None
+ """The template used for this verification."""
diff --git a/src/prelude_python_sdk/types/verification/phone/phone_verification_carrier.py b/src/prelude_python_sdk/types/verification/phone/phone_verification_carrier.py
new file mode 100644
index 00000000..a016a529
--- /dev/null
+++ b/src/prelude_python_sdk/types/verification/phone/phone_verification_carrier.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ...._models import BaseModel
+
+__all__ = ["PhoneVerificationCarrier"]
+
+
+class PhoneVerificationCarrier(BaseModel):
+ """The end user's mobile network."""
+
+ mccmnc: str
+
+ name: Optional[str] = None
diff --git a/src/prelude_python_sdk/types/verification/phone/phone_verification_money.py b/src/prelude_python_sdk/types/verification/phone/phone_verification_money.py
new file mode 100644
index 00000000..ae6e5095
--- /dev/null
+++ b/src/prelude_python_sdk/types/verification/phone/phone_verification_money.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ...._models import BaseModel
+
+__all__ = ["PhoneVerificationMoney"]
+
+
+class PhoneVerificationMoney(BaseModel):
+ amount: str
+ """Exact decimal amount.
+
+ It is never rounded to the currency's minor units, so a sub-cent cost reads as
+ `0.0004` rather than as `0.00`.
+ """
+
+ currency: str
+ """ISO 4217 currency code."""
diff --git a/src/prelude_python_sdk/types/verification/phone/phone_verification_psd2_transaction.py b/src/prelude_python_sdk/types/verification/phone/phone_verification_psd2_transaction.py
new file mode 100644
index 00000000..a3f7d531
--- /dev/null
+++ b/src/prelude_python_sdk/types/verification/phone/phone_verification_psd2_transaction.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ...._models import BaseModel
+from .phone_verification_money import PhoneVerificationMoney
+
+__all__ = ["PhoneVerificationPsd2Transaction"]
+
+
+class PhoneVerificationPsd2Transaction(BaseModel):
+ amount: Optional[PhoneVerificationMoney] = None
+
+ recipient: Optional[str] = None
+ """Payee name displayed to the payer."""
diff --git a/src/prelude_python_sdk/types/verification_check_params.py b/src/prelude_python_sdk/types/verification_check_params.py
index 139376e7..486b313e 100644
--- a/src/prelude_python_sdk/types/verification_check_params.py
+++ b/src/prelude_python_sdk/types/verification_check_params.py
@@ -2,9 +2,11 @@
from __future__ import annotations
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import Required, TypedDict
-__all__ = ["VerificationCheckParams", "Target", "Psd2"]
+from .shared_params.target import Target
+
+__all__ = ["VerificationCheckParams", "Psd2"]
class VerificationCheckParams(TypedDict, total=False):
@@ -27,19 +29,6 @@ class VerificationCheckParams(TypedDict, total=False):
"""
-class Target(TypedDict, total=False):
- """The verification target.
-
- Either a phone number or an email address. To use the email verification feature contact us to discuss your use case.
- """
-
- type: Required[Literal["phone_number", "email_address"]]
- """The type of the target. Either "phone_number" or "email_address"."""
-
- value: Required[str]
- """An E.164 formatted phone number or an email address."""
-
-
class Psd2(TypedDict, total=False):
"""Required when checking a code issued under the `prelude:psd2` template.
diff --git a/src/prelude_python_sdk/types/verification_create_params.py b/src/prelude_python_sdk/types/verification_create_params.py
index 73efc284..cc1bf07c 100644
--- a/src/prelude_python_sdk/types/verification_create_params.py
+++ b/src/prelude_python_sdk/types/verification_create_params.py
@@ -5,7 +5,10 @@
from typing import Dict, List
from typing_extensions import Literal, Required, TypedDict
-__all__ = ["VerificationCreateParams", "Target", "Metadata", "Options", "OptionsAppRealm", "Signals"]
+from .shared_params.target import Target
+from .shared_params.signals import Signals
+
+__all__ = ["VerificationCreateParams", "Metadata", "Options", "OptionsAppRealm"]
class VerificationCreateParams(TypedDict, total=False):
@@ -37,19 +40,6 @@ class VerificationCreateParams(TypedDict, total=False):
"""
-class Target(TypedDict, total=False):
- """The verification target.
-
- Either a phone number or an email address. To use the email verification feature contact us to discuss your use case.
- """
-
- type: Required[Literal["phone_number", "email_address"]]
- """The type of the target. Either "phone_number" or "email_address"."""
-
- value: Required[str]
- """An E.164 formatted phone number or an email address."""
-
-
class Metadata(TypedDict, total=False):
"""The metadata for this verification.
@@ -204,70 +194,3 @@ class Options(TypedDict, total=False):
variables: Dict[str, str]
"""The variables to be replaced in the template."""
-
-
-class Signals(TypedDict, total=False):
- """The signals used for anti-fraud.
-
- For more details, refer to [Signals](/verify/v2/documentation/prevent-fraud#signals).
- """
-
- app_version: str
- """The version of your application."""
-
- device_id: str
- """A unique ID for the user's device.
-
- You should ensure that each user device has a unique `device_id` value. Ideally,
- for Android, this corresponds to the `ANDROID_ID` and for iOS, this corresponds
- to the `identifierForVendor`.
- """
-
- device_model: str
- """The model of the user's device."""
-
- device_platform: Literal["android", "ios", "ipados", "tvos", "web"]
- """The type of the user's device."""
-
- existing_user: bool
- """
- Whether the end-user already exists in your system, for example an existing
- account signing in again rather than a first-time signup. Unlike
- `is_trusted_user`, this signal does not bypass fraud checks; it is taken into
- account as one additional anti-fraud signal. For more details, refer to
- [Signals](/verify/v2/documentation/prevent-fraud#signals).
- """
-
- ip: str
- """The public IP v4 or v6 address of the end-user's device.
-
- You should collect this from your backend. If your backend is behind a proxy,
- use the `X-Forwarded-For`, `Forwarded`, `True-Client-IP`, `CF-Connecting-IP` or
- an equivalent header to get the actual public IP of the end-user's device.
- """
-
- is_trusted_user: bool
- """
- This signal should indicate a higher level of trust, explicitly stating that the
- user is genuine. Contact us to discuss your use case. For more details, refer to
- [Signals](/verify/v2/documentation/prevent-fraud#signals).
- """
-
- ja4_fingerprint: str
- """The JA4 fingerprint observed for the end-user's connection.
-
- Prelude will infer it automatically when you use our Frontend SDKs (which use
- Prelude's edge network), but you can also forward the value if you terminate TLS
- yourself.
- """
-
- os_version: str
- """The version of the user's device operating system."""
-
- user_agent: str
- """The user agent of the user's device.
-
- If the individual fields (os_version, device_platform, device_model) are
- provided, we will prioritize those values instead of parsing them from the user
- agent string.
- """
diff --git a/src/prelude_python_sdk/types/verification_management/__init__.py b/src/prelude_python_sdk/types/verification_management/__init__.py
new file mode 100644
index 00000000..f67e8284
--- /dev/null
+++ b/src/prelude_python_sdk/types/verification_management/__init__.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .sandbox_add_phone_number_params import SandboxAddPhoneNumberParams as SandboxAddPhoneNumberParams
+from .sandbox_add_phone_number_response import SandboxAddPhoneNumberResponse as SandboxAddPhoneNumberResponse
+from .sandbox_list_phone_numbers_response import SandboxListPhoneNumbersResponse as SandboxListPhoneNumbersResponse
+from .sandbox_delete_phone_number_response import SandboxDeletePhoneNumberResponse as SandboxDeletePhoneNumberResponse
diff --git a/src/prelude_python_sdk/types/verification_management/sandbox_add_phone_number_params.py b/src/prelude_python_sdk/types/verification_management/sandbox_add_phone_number_params.py
new file mode 100644
index 00000000..daf5e7d0
--- /dev/null
+++ b/src/prelude_python_sdk/types/verification_management/sandbox_add_phone_number_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["SandboxAddPhoneNumberParams"]
+
+
+class SandboxAddPhoneNumberParams(TypedDict, total=False):
+ attempt_code: Required[str]
+ """
+ The fixed attempt code that will validate verification attempts for this phone
+ number.
+ """
+
+ phone_number: Required[str]
+ """An E.164 formatted phone number to add to the sandbox list."""
diff --git a/src/prelude_python_sdk/types/verification_management/sandbox_add_phone_number_response.py b/src/prelude_python_sdk/types/verification_management/sandbox_add_phone_number_response.py
new file mode 100644
index 00000000..d73ac1e3
--- /dev/null
+++ b/src/prelude_python_sdk/types/verification_management/sandbox_add_phone_number_response.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+
+__all__ = ["SandboxAddPhoneNumberResponse"]
+
+
+class SandboxAddPhoneNumberResponse(BaseModel):
+ attempt_code: str
+ """The fixed attempt code associated with the sandbox phone number."""
+
+ phone_number: str
+ """The E.164 formatted phone number that was added to the sandbox list."""
diff --git a/src/prelude_python_sdk/types/verification_management/sandbox_delete_phone_number_response.py b/src/prelude_python_sdk/types/verification_management/sandbox_delete_phone_number_response.py
new file mode 100644
index 00000000..f12c1bf1
--- /dev/null
+++ b/src/prelude_python_sdk/types/verification_management/sandbox_delete_phone_number_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+
+__all__ = ["SandboxDeletePhoneNumberResponse"]
+
+
+class SandboxDeletePhoneNumberResponse(BaseModel):
+ phone_number: str
+ """The E.164 formatted phone number that was removed from the sandbox list."""
diff --git a/src/prelude_python_sdk/types/verification_management/sandbox_list_phone_numbers_response.py b/src/prelude_python_sdk/types/verification_management/sandbox_list_phone_numbers_response.py
new file mode 100644
index 00000000..deed7f78
--- /dev/null
+++ b/src/prelude_python_sdk/types/verification_management/sandbox_list_phone_numbers_response.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["SandboxListPhoneNumbersResponse", "PhoneNumber"]
+
+
+class PhoneNumber(BaseModel):
+ attempt_code: str
+ """The fixed attempt code associated with the sandbox phone number."""
+
+ created_at: datetime
+ """The date and time when the phone number was added to the sandbox list."""
+
+ phone_number: str
+ """An E.164 formatted phone number."""
+
+
+class SandboxListPhoneNumbersResponse(BaseModel):
+ phone_numbers: List[PhoneNumber]
+ """A list of sandbox phone numbers."""
diff --git a/src/prelude_python_sdk/types/watch_evaluate_params.py b/src/prelude_python_sdk/types/watch_evaluate_params.py
new file mode 100644
index 00000000..1cbc9183
--- /dev/null
+++ b/src/prelude_python_sdk/types/watch_evaluate_params.py
@@ -0,0 +1,48 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import Required, TypedDict
+
+from .shared_params.target import Target
+from .shared_params.signals import Signals
+
+__all__ = ["WatchEvaluateParams"]
+
+
+class WatchEvaluateParams(TypedDict, total=False):
+ flow_id: Required[str]
+ """The flow to evaluate.
+
+ A flow names the moment you are guarding and selects the recipes that run.
+ """
+
+ target: Required[Target]
+ """The identifier to score — a phone number or email address."""
+
+ attributes: Dict[str, str]
+ """
+ Values for the attributes the flow's recipes declare, keyed without the `attr.`
+ namespace a rule uses to reference them.
+
+ An attribute a recipe declares and this request omits is treated as missing
+ evidence, not as an empty value: the rules reading it report `NOT_EVALUATED`
+ rather than being scored as though the condition were false. A key no recipe in
+ the flow declares is ignored rather than rejected, so one payload can serve
+ flows that read different attributes.
+ """
+
+ dispatch_id: str
+ """The identifier of the dispatch that came from the front-end SDK.
+
+ Signals it carries fill in anything the request did not state; the request wins
+ where both supply a value.
+ """
+
+ signals: Signals
+ """The signals used for anti-fraud.
+
+ For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
+ """
diff --git a/src/prelude_python_sdk/types/watch_evaluate_response.py b/src/prelude_python_sdk/types/watch_evaluate_response.py
new file mode 100644
index 00000000..a9b6def5
--- /dev/null
+++ b/src/prelude_python_sdk/types/watch_evaluate_response.py
@@ -0,0 +1,125 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["WatchEvaluateResponse", "Recipe", "RecipeRule"]
+
+
+class RecipeRule(BaseModel):
+ outcome: Literal["TRIGGERED", "NOT_TRIGGERED", "NOT_EVALUATED"]
+ """What the rule concluded.
+
+ - `TRIGGERED` - The condition held; `weight` was added to the score.
+ - `NOT_TRIGGERED` - The condition did not hold.
+ - `NOT_EVALUATED` - The rule could not run, because something it reads never
+ arrived. This is not a quieter `NOT_TRIGGERED`: it contributed nothing either
+ way, and it is why `partial_evidence` is set on the recipe.
+ """
+
+ rule_id: str
+ """The rule that produced this result.
+
+ Present whatever the rule's visibility, so a rule you cannot see the condition
+ of is still one you can reweight, switch off, or ask us about.
+ """
+
+ weight: int
+ """What this rule contributes to the recipe's score when it triggers."""
+
+ blocked_by: Optional[str] = None
+ """Why the rule could not run, set only when `outcome` is `NOT_EVALUATED`.
+
+ A rule you authored names the signal or attribute it waited on, since you wrote
+ the expression that reads it. A Prelude-managed rule reports `missing_data` and
+ nothing more: the signal it waited on is part of a condition that is not
+ disclosed.
+ """
+
+ name: Optional[str] = None
+ """
+ The rule's name, present for a rule you authored and omitted for a
+ Prelude-managed one. A managed rule's name describes what it looks for, which is
+ as much of the condition as the expression is.
+ """
+
+ unavailable: Optional[bool] = None
+ """
+ The rule could not run for a reason on our side rather than anything about your
+ request. `outcome` is `NOT_EVALUATED` and the failure is ours to fix.
+ """
+
+
+class Recipe(BaseModel):
+ partial_evidence: bool
+ """
+ At least one rule could not be evaluated, so the score rests on less than the
+ whole recipe. The score is still returned — a partial verdict is more useful
+ than none — but it is labeled rather than passed off as whole.
+ """
+
+ recipe_id: str
+ """The recipe that produced this result."""
+
+ rules: List[RecipeRule]
+ """One result per rule in the recipe, in membership order.
+
+ Every rule runs — a score is only meaningful when complete, so there is no
+ short-circuit on the first trigger.
+ """
+
+ score: int
+ """
+ The sum of the weights of the rules that triggered, clamped to the range -100
+ to 100. Two scores at a bound are not comparable.
+ """
+
+ threshold: int
+ """The score at or above which this recipe flags."""
+
+ verdict: Literal["PASS", "FLAG"]
+ """This recipe's own verdict.
+
+ Normally the score against the threshold, unless a preempting rule fired — see
+ `determined_by`.
+ """
+
+ determined_by: Optional[str] = None
+ """
+ The preempting rule that set `verdict`, present only when a rule rather than the
+ score decided it. Without it a recipe can report a score under its threshold and
+ still flag, with nothing in the payload accounting for the difference.
+ """
+
+
+class WatchEvaluateResponse(BaseModel):
+ id: str
+ """The evaluation identifier."""
+
+ action: Literal["ALLOW", "BLOCK", "CHALLENGE"]
+ """
+ What the evaluation suggests you do, being the most severe action across the
+ recipes that ran. Advisory: enforcement is yours.
+
+ - `ALLOW` - Let the request through.
+ - `BLOCK` - Refuse the request.
+ - `CHALLENGE` - Let the request through behind an additional check.
+ """
+
+ recipes: List[Recipe]
+ """One result per recipe that ran.
+
+ A recipe the flow names but that is not in service is absent rather than
+ reported as having passed.
+ """
+
+ verdict: Literal["PASS", "FLAG"]
+ """
+ The evaluation-level verdict, being the most severe verdict across the recipes
+ that ran.
+
+ - `PASS` - No recipe flagged.
+ - `FLAG` - At least one recipe flagged.
+ """
diff --git a/src/prelude_python_sdk/types/watch_predict_params.py b/src/prelude_python_sdk/types/watch_predict_params.py
index 93a04966..a70ffdca 100644
--- a/src/prelude_python_sdk/types/watch_predict_params.py
+++ b/src/prelude_python_sdk/types/watch_predict_params.py
@@ -2,9 +2,12 @@
from __future__ import annotations
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import Required, TypedDict
-__all__ = ["WatchPredictParams", "Target", "Metadata", "Signals"]
+from .shared_params.target import Target
+from .shared_params.signals import Signals
+
+__all__ = ["WatchPredictParams", "Metadata"]
class WatchPredictParams(TypedDict, total=False):
@@ -25,16 +28,6 @@ class WatchPredictParams(TypedDict, total=False):
"""
-class Target(TypedDict, total=False):
- """The signup identifier to score — a phone number or email address."""
-
- type: Required[Literal["phone_number", "email_address"]]
- """The type of the target. Either "phone_number" or "email_address"."""
-
- value: Required[str]
- """An E.164 formatted phone number or an email address."""
-
-
class Metadata(TypedDict, total=False):
"""The metadata for this prediction."""
@@ -44,70 +37,3 @@ class Metadata(TypedDict, total=False):
It is returned in the response and any webhook events that refer to this
prediction.
"""
-
-
-class Signals(TypedDict, total=False):
- """The signals used for anti-fraud.
-
- For more details, refer to [Signals](/verify/v2/documentation/prevent-fraud#signals).
- """
-
- app_version: str
- """The version of your application."""
-
- device_id: str
- """A unique ID for the user's device.
-
- You should ensure that each user device has a unique `device_id` value. Ideally,
- for Android, this corresponds to the `ANDROID_ID` and for iOS, this corresponds
- to the `identifierForVendor`.
- """
-
- device_model: str
- """The model of the user's device."""
-
- device_platform: Literal["android", "ios", "ipados", "tvos", "web"]
- """The type of the user's device."""
-
- existing_user: bool
- """
- Whether the end-user already exists in your system, for example an existing
- account signing in again rather than a first-time signup. Unlike
- `is_trusted_user`, this signal does not bypass fraud checks; it is taken into
- account as one additional anti-fraud signal. For more details, refer to
- [Signals](/verify/v2/documentation/prevent-fraud#signals).
- """
-
- ip: str
- """The public IP v4 or v6 address of the end-user's device.
-
- You should collect this from your backend. If your backend is behind a proxy,
- use the `X-Forwarded-For`, `Forwarded`, `True-Client-IP`, `CF-Connecting-IP` or
- an equivalent header to get the actual public IP of the end-user's device.
- """
-
- is_trusted_user: bool
- """
- This signal should indicate a higher level of trust, explicitly stating that the
- user is genuine. Contact us to discuss your use case. For more details, refer to
- [Signals](/verify/v2/documentation/prevent-fraud#signals).
- """
-
- ja4_fingerprint: str
- """The JA4 fingerprint observed for the end-user's connection.
-
- Prelude will infer it automatically when you use our Frontend SDKs (which use
- Prelude's edge network), but you can also forward the value if you terminate TLS
- yourself.
- """
-
- os_version: str
- """The version of the user's device operating system."""
-
- user_agent: str
- """The user agent of the user's device.
-
- If the individual fields (os_version, device_platform, device_model) are
- provided, we will prioritize those values instead of parsing them from the user
- agent string.
- """
diff --git a/src/prelude_python_sdk/types/watch_send_events_params.py b/src/prelude_python_sdk/types/watch_send_events_params.py
index 29d73caa..4adc34a2 100644
--- a/src/prelude_python_sdk/types/watch_send_events_params.py
+++ b/src/prelude_python_sdk/types/watch_send_events_params.py
@@ -5,7 +5,9 @@
from typing import Iterable
from typing_extensions import Literal, Required, TypedDict
-__all__ = ["WatchSendEventsParams", "Event", "EventTarget"]
+from .shared_params.target import Target
+
+__all__ = ["WatchSendEventsParams", "Event"]
class WatchSendEventsParams(TypedDict, total=False):
@@ -16,16 +18,6 @@ class WatchSendEventsParams(TypedDict, total=False):
"""
-class EventTarget(TypedDict, total=False):
- """The event target. Only supports phone numbers for now."""
-
- type: Required[Literal["phone_number", "email_address"]]
- """The type of the target. Either "phone_number" or "email_address"."""
-
- value: Required[str]
- """An E.164 formatted phone number or an email address."""
-
-
class Event(TypedDict, total=False):
confidence: Required[Literal["maximum", "high", "neutral", "low", "minimum"]]
"""
@@ -43,5 +35,5 @@ class Event(TypedDict, total=False):
label: Required[str]
"""A label to describe what the event refers to."""
- target: Required[EventTarget]
+ target: Required[Target]
"""The event target. Only supports phone numbers for now."""
diff --git a/src/prelude_python_sdk/types/watch_send_feedbacks_params.py b/src/prelude_python_sdk/types/watch_send_feedbacks_params.py
index 5eb13414..90f08af8 100644
--- a/src/prelude_python_sdk/types/watch_send_feedbacks_params.py
+++ b/src/prelude_python_sdk/types/watch_send_feedbacks_params.py
@@ -5,7 +5,9 @@
from typing import Iterable
from typing_extensions import Literal, Required, TypedDict
-__all__ = ["WatchSendFeedbacksParams", "Feedback", "FeedbackTarget", "FeedbackMetadata"]
+from .shared_params.target import Target
+
+__all__ = ["WatchSendFeedbacksParams", "Feedback", "FeedbackMetadata"]
class WatchSendFeedbacksParams(TypedDict, total=False):
@@ -16,16 +18,6 @@ class WatchSendFeedbacksParams(TypedDict, total=False):
"""
-class FeedbackTarget(TypedDict, total=False):
- """The feedback target. Only supports phone numbers for now."""
-
- type: Required[Literal["phone_number", "email_address"]]
- """The type of the target. Either "phone_number" or "email_address"."""
-
- value: Required[str]
- """An E.164 formatted phone number or an email address."""
-
-
class FeedbackMetadata(TypedDict, total=False):
"""The metadata for this feedback."""
@@ -38,7 +30,7 @@ class FeedbackMetadata(TypedDict, total=False):
class Feedback(TypedDict, total=False):
- target: Required[FeedbackTarget]
+ target: Required[Target]
"""The feedback target. Only supports phone numbers for now."""
type: Required[Literal["verification.started", "verification.completed"]]
diff --git a/tests/api_resources/intel/__init__.py b/tests/api_resources/intel/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/intel/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/intel/test_kyc.py b/tests/api_resources/intel/test_kyc.py
new file mode 100644
index 00000000..88f4dc71
--- /dev/null
+++ b/tests/api_resources/intel/test_kyc.py
@@ -0,0 +1,133 @@
+# 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 tests.utils import assert_matches_type
+from prelude_python_sdk import Prelude, AsyncPrelude
+from prelude_python_sdk._utils import parse_date
+from prelude_python_sdk.types.intel import KYCMatchResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestKYC:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_match(self, client: Prelude) -> None:
+ kyc = client.intel.kyc.match(
+ phone="+12065550100",
+ )
+ assert_matches_type(KYCMatchResponse, kyc, path=["response"])
+
+ @parametrize
+ def test_method_match_with_all_params(self, client: Prelude) -> None:
+ kyc = client.intel.kyc.match(
+ phone="+12065550100",
+ address="12 rue de la Paix",
+ birthdate=parse_date("1990-01-15"),
+ country="FR",
+ email="jean.dupont@example.com",
+ family_name="Dupont",
+ given_name="Jean",
+ locality="Paris",
+ postal_code="75002",
+ region="Île-de-France",
+ )
+ assert_matches_type(KYCMatchResponse, kyc, path=["response"])
+
+ @parametrize
+ def test_raw_response_match(self, client: Prelude) -> None:
+ response = client.intel.kyc.with_raw_response.match(
+ phone="+12065550100",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ kyc = response.parse()
+ assert_matches_type(KYCMatchResponse, kyc, path=["response"])
+
+ @parametrize
+ def test_streaming_response_match(self, client: Prelude) -> None:
+ with client.intel.kyc.with_streaming_response.match(
+ phone="+12065550100",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ kyc = response.parse()
+ assert_matches_type(KYCMatchResponse, kyc, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_match(self, client: Prelude) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `phone` but received ''"):
+ client.intel.kyc.with_raw_response.match(
+ phone="",
+ )
+
+
+class TestAsyncKYC:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_match(self, async_client: AsyncPrelude) -> None:
+ kyc = await async_client.intel.kyc.match(
+ phone="+12065550100",
+ )
+ assert_matches_type(KYCMatchResponse, kyc, path=["response"])
+
+ @parametrize
+ async def test_method_match_with_all_params(self, async_client: AsyncPrelude) -> None:
+ kyc = await async_client.intel.kyc.match(
+ phone="+12065550100",
+ address="12 rue de la Paix",
+ birthdate=parse_date("1990-01-15"),
+ country="FR",
+ email="jean.dupont@example.com",
+ family_name="Dupont",
+ given_name="Jean",
+ locality="Paris",
+ postal_code="75002",
+ region="Île-de-France",
+ )
+ assert_matches_type(KYCMatchResponse, kyc, path=["response"])
+
+ @parametrize
+ async def test_raw_response_match(self, async_client: AsyncPrelude) -> None:
+ response = await async_client.intel.kyc.with_raw_response.match(
+ phone="+12065550100",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ kyc = await response.parse()
+ assert_matches_type(KYCMatchResponse, kyc, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_match(self, async_client: AsyncPrelude) -> None:
+ async with async_client.intel.kyc.with_streaming_response.match(
+ phone="+12065550100",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ kyc = await response.parse()
+ assert_matches_type(KYCMatchResponse, kyc, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_match(self, async_client: AsyncPrelude) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `phone` but received ''"):
+ await async_client.intel.kyc.with_raw_response.match(
+ phone="",
+ )
diff --git a/tests/api_resources/test_notify.py b/tests/api_resources/test_notify.py
index db32ff7c..5b14f041 100644
--- a/tests/api_resources/test_notify.py
+++ b/tests/api_resources/test_notify.py
@@ -11,6 +11,7 @@
from prelude_python_sdk import Prelude, AsyncPrelude
from prelude_python_sdk.types import (
NotifySendResponse,
+ NotifyReplyResponse,
NotifySendBatchResponse,
NotifyGetSubscriptionConfigResponse,
NotifyListSubscriptionConfigsResponse,
@@ -251,6 +252,54 @@ def test_path_params_list_subscription_phone_numbers(self, client: Prelude) -> N
config_id="",
)
+ @parametrize
+ def test_method_reply(self, client: Prelude) -> None:
+ notify = client.notify.reply(
+ reply_to="im_01k8aq2zggeyssvt53zgvpx63a",
+ text="Thanks for reaching out! We'll look into your request.",
+ to="+33612345678",
+ )
+ assert_matches_type(NotifyReplyResponse, notify, path=["response"])
+
+ @parametrize
+ def test_method_reply_with_all_params(self, client: Prelude) -> None:
+ notify = client.notify.reply(
+ reply_to="im_01k8aq2zggeyssvt53zgvpx63a",
+ text="Thanks for reaching out! We'll look into your request.",
+ to="+33612345678",
+ callback_url="https://your-app.com/webhooks/notify",
+ correlation_id="support-ticket-42",
+ )
+ assert_matches_type(NotifyReplyResponse, notify, path=["response"])
+
+ @parametrize
+ def test_raw_response_reply(self, client: Prelude) -> None:
+ response = client.notify.with_raw_response.reply(
+ reply_to="im_01k8aq2zggeyssvt53zgvpx63a",
+ text="Thanks for reaching out! We'll look into your request.",
+ to="+33612345678",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ notify = response.parse()
+ assert_matches_type(NotifyReplyResponse, notify, path=["response"])
+
+ @parametrize
+ def test_streaming_response_reply(self, client: Prelude) -> None:
+ with client.notify.with_streaming_response.reply(
+ reply_to="im_01k8aq2zggeyssvt53zgvpx63a",
+ text="Thanks for reaching out! We'll look into your request.",
+ to="+33612345678",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ notify = response.parse()
+ assert_matches_type(NotifyReplyResponse, notify, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
@parametrize
def test_method_send(self, client: Prelude) -> None:
notify = client.notify.send(
@@ -600,6 +649,54 @@ async def test_path_params_list_subscription_phone_numbers(self, async_client: A
config_id="",
)
+ @parametrize
+ async def test_method_reply(self, async_client: AsyncPrelude) -> None:
+ notify = await async_client.notify.reply(
+ reply_to="im_01k8aq2zggeyssvt53zgvpx63a",
+ text="Thanks for reaching out! We'll look into your request.",
+ to="+33612345678",
+ )
+ assert_matches_type(NotifyReplyResponse, notify, path=["response"])
+
+ @parametrize
+ async def test_method_reply_with_all_params(self, async_client: AsyncPrelude) -> None:
+ notify = await async_client.notify.reply(
+ reply_to="im_01k8aq2zggeyssvt53zgvpx63a",
+ text="Thanks for reaching out! We'll look into your request.",
+ to="+33612345678",
+ callback_url="https://your-app.com/webhooks/notify",
+ correlation_id="support-ticket-42",
+ )
+ assert_matches_type(NotifyReplyResponse, notify, path=["response"])
+
+ @parametrize
+ async def test_raw_response_reply(self, async_client: AsyncPrelude) -> None:
+ response = await async_client.notify.with_raw_response.reply(
+ reply_to="im_01k8aq2zggeyssvt53zgvpx63a",
+ text="Thanks for reaching out! We'll look into your request.",
+ to="+33612345678",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ notify = await response.parse()
+ assert_matches_type(NotifyReplyResponse, notify, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_reply(self, async_client: AsyncPrelude) -> None:
+ async with async_client.notify.with_streaming_response.reply(
+ reply_to="im_01k8aq2zggeyssvt53zgvpx63a",
+ text="Thanks for reaching out! We'll look into your request.",
+ to="+33612345678",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ notify = await response.parse()
+ assert_matches_type(NotifyReplyResponse, notify, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
@parametrize
async def test_method_send(self, async_client: AsyncPrelude) -> None:
notify = await async_client.notify.send(
diff --git a/tests/api_resources/test_watch.py b/tests/api_resources/test_watch.py
index 95343e77..60d12510 100644
--- a/tests/api_resources/test_watch.py
+++ b/tests/api_resources/test_watch.py
@@ -11,6 +11,7 @@
from prelude_python_sdk import Prelude, AsyncPrelude
from prelude_python_sdk.types import (
WatchPredictResponse,
+ WatchEvaluateResponse,
WatchSendEventsResponse,
WatchSendFeedbacksResponse,
)
@@ -21,6 +22,77 @@
class TestWatch:
parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+ @parametrize
+ def test_method_evaluate(self, client: Prelude) -> None:
+ watch = client.watch.evaluate(
+ flow_id="flo_01jc0t6fwwfgfsq1md24mhyztj",
+ target={
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ )
+ assert_matches_type(WatchEvaluateResponse, watch, path=["response"])
+
+ @parametrize
+ def test_method_evaluate_with_all_params(self, client: Prelude) -> None:
+ watch = client.watch.evaluate(
+ flow_id="flo_01jc0t6fwwfgfsq1md24mhyztj",
+ target={
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ attributes={
+ "plan_tier": "free",
+ "account_age_days": "3",
+ },
+ dispatch_id="123e4567-e89b-12d3-a456-426614174000",
+ signals={
+ "app_version": "1.2.34",
+ "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2",
+ "device_model": "iPhone17,2",
+ "device_platform": "ios",
+ "existing_user": False,
+ "ip": "203.0.113.123",
+ "is_trusted_user": False,
+ "ja4_fingerprint": "t13d1516h2_8daaf6152771_e5627efa2ab1",
+ "os_version": "18.0.1",
+ "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1",
+ },
+ )
+ assert_matches_type(WatchEvaluateResponse, watch, path=["response"])
+
+ @parametrize
+ def test_raw_response_evaluate(self, client: Prelude) -> None:
+ response = client.watch.with_raw_response.evaluate(
+ flow_id="flo_01jc0t6fwwfgfsq1md24mhyztj",
+ target={
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ watch = response.parse()
+ assert_matches_type(WatchEvaluateResponse, watch, path=["response"])
+
+ @parametrize
+ def test_streaming_response_evaluate(self, client: Prelude) -> None:
+ with client.watch.with_streaming_response.evaluate(
+ flow_id="flo_01jc0t6fwwfgfsq1md24mhyztj",
+ target={
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ watch = response.parse()
+ assert_matches_type(WatchEvaluateResponse, watch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
@parametrize
def test_method_predict(self, client: Prelude) -> None:
watch = client.watch.predict(
@@ -204,6 +276,77 @@ class TestAsyncWatch:
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
+ @parametrize
+ async def test_method_evaluate(self, async_client: AsyncPrelude) -> None:
+ watch = await async_client.watch.evaluate(
+ flow_id="flo_01jc0t6fwwfgfsq1md24mhyztj",
+ target={
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ )
+ assert_matches_type(WatchEvaluateResponse, watch, path=["response"])
+
+ @parametrize
+ async def test_method_evaluate_with_all_params(self, async_client: AsyncPrelude) -> None:
+ watch = await async_client.watch.evaluate(
+ flow_id="flo_01jc0t6fwwfgfsq1md24mhyztj",
+ target={
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ attributes={
+ "plan_tier": "free",
+ "account_age_days": "3",
+ },
+ dispatch_id="123e4567-e89b-12d3-a456-426614174000",
+ signals={
+ "app_version": "1.2.34",
+ "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2",
+ "device_model": "iPhone17,2",
+ "device_platform": "ios",
+ "existing_user": False,
+ "ip": "203.0.113.123",
+ "is_trusted_user": False,
+ "ja4_fingerprint": "t13d1516h2_8daaf6152771_e5627efa2ab1",
+ "os_version": "18.0.1",
+ "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1",
+ },
+ )
+ assert_matches_type(WatchEvaluateResponse, watch, path=["response"])
+
+ @parametrize
+ async def test_raw_response_evaluate(self, async_client: AsyncPrelude) -> None:
+ response = await async_client.watch.with_raw_response.evaluate(
+ flow_id="flo_01jc0t6fwwfgfsq1md24mhyztj",
+ target={
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ watch = await response.parse()
+ assert_matches_type(WatchEvaluateResponse, watch, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_evaluate(self, async_client: AsyncPrelude) -> None:
+ async with async_client.watch.with_streaming_response.evaluate(
+ flow_id="flo_01jc0t6fwwfgfsq1md24mhyztj",
+ target={
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ watch = await response.parse()
+ assert_matches_type(WatchEvaluateResponse, watch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
@parametrize
async def test_method_predict(self, async_client: AsyncPrelude) -> None:
watch = await async_client.watch.predict(
diff --git a/tests/api_resources/verification/__init__.py b/tests/api_resources/verification/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/verification/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/verification/phone/__init__.py b/tests/api_resources/verification/phone/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/verification/phone/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/verification/phone/test_history.py b/tests/api_resources/verification/phone/test_history.py
new file mode 100644
index 00000000..705402fd
--- /dev/null
+++ b/tests/api_resources/verification/phone/test_history.py
@@ -0,0 +1,190 @@
+# 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 tests.utils import assert_matches_type
+from prelude_python_sdk import Prelude, AsyncPrelude
+from prelude_python_sdk._utils import parse_datetime
+from prelude_python_sdk.types.verification.phone import (
+ HistoryListResponse,
+ HistoryRetrieveResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestHistory:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_retrieve(self, client: Prelude) -> None:
+ history = client.verification.phone.history.retrieve(
+ "vrf_01jc0t6fwwfgfsq1md24mhyztj",
+ )
+ assert_matches_type(HistoryRetrieveResponse, history, path=["response"])
+
+ @parametrize
+ def test_raw_response_retrieve(self, client: Prelude) -> None:
+ response = client.verification.phone.history.with_raw_response.retrieve(
+ "vrf_01jc0t6fwwfgfsq1md24mhyztj",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ history = response.parse()
+ assert_matches_type(HistoryRetrieveResponse, history, path=["response"])
+
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Prelude) -> None:
+ with client.verification.phone.history.with_streaming_response.retrieve(
+ "vrf_01jc0t6fwwfgfsq1md24mhyztj",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ history = response.parse()
+ assert_matches_type(HistoryRetrieveResponse, history, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_retrieve(self, client: Prelude) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.verification.phone.history.with_raw_response.retrieve(
+ "",
+ )
+
+ @parametrize
+ def test_method_list(self, client: Prelude) -> None:
+ history = client.verification.phone.history.list()
+ assert_matches_type(HistoryListResponse, history, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Prelude) -> None:
+ history = client.verification.phone.history.list(
+ channels=["sms"],
+ cursor="cursor",
+ device_platform="android",
+ from_=parse_datetime("2026-09-01T00:00:00Z"),
+ limit=1,
+ max_attempts=0,
+ min_attempts=0,
+ phone_number="+33612345678",
+ region="FR",
+ status="converted",
+ template_id="template_01jc0t6fwwfgfsq1md24mhyztj",
+ to=parse_datetime("2026-09-08T00:00:00Z"),
+ )
+ assert_matches_type(HistoryListResponse, history, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Prelude) -> None:
+ response = client.verification.phone.history.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ history = response.parse()
+ assert_matches_type(HistoryListResponse, history, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Prelude) -> None:
+ with client.verification.phone.history.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ history = response.parse()
+ assert_matches_type(HistoryListResponse, history, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncHistory:
+ 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: AsyncPrelude) -> None:
+ history = await async_client.verification.phone.history.retrieve(
+ "vrf_01jc0t6fwwfgfsq1md24mhyztj",
+ )
+ assert_matches_type(HistoryRetrieveResponse, history, path=["response"])
+
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncPrelude) -> None:
+ response = await async_client.verification.phone.history.with_raw_response.retrieve(
+ "vrf_01jc0t6fwwfgfsq1md24mhyztj",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ history = await response.parse()
+ assert_matches_type(HistoryRetrieveResponse, history, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncPrelude) -> None:
+ async with async_client.verification.phone.history.with_streaming_response.retrieve(
+ "vrf_01jc0t6fwwfgfsq1md24mhyztj",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ history = await response.parse()
+ assert_matches_type(HistoryRetrieveResponse, history, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncPrelude) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.verification.phone.history.with_raw_response.retrieve(
+ "",
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncPrelude) -> None:
+ history = await async_client.verification.phone.history.list()
+ assert_matches_type(HistoryListResponse, history, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncPrelude) -> None:
+ history = await async_client.verification.phone.history.list(
+ channels=["sms"],
+ cursor="cursor",
+ device_platform="android",
+ from_=parse_datetime("2026-09-01T00:00:00Z"),
+ limit=1,
+ max_attempts=0,
+ min_attempts=0,
+ phone_number="+33612345678",
+ region="FR",
+ status="converted",
+ template_id="template_01jc0t6fwwfgfsq1md24mhyztj",
+ to=parse_datetime("2026-09-08T00:00:00Z"),
+ )
+ assert_matches_type(HistoryListResponse, history, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncPrelude) -> None:
+ response = await async_client.verification.phone.history.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ history = await response.parse()
+ assert_matches_type(HistoryListResponse, history, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncPrelude) -> None:
+ async with async_client.verification.phone.history.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ history = await response.parse()
+ assert_matches_type(HistoryListResponse, history, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/verification_management/__init__.py b/tests/api_resources/verification_management/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/verification_management/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/verification_management/test_sandbox.py b/tests/api_resources/verification_management/test_sandbox.py
new file mode 100644
index 00000000..36eae1cc
--- /dev/null
+++ b/tests/api_resources/verification_management/test_sandbox.py
@@ -0,0 +1,224 @@
+# 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 tests.utils import assert_matches_type
+from prelude_python_sdk import Prelude, AsyncPrelude
+from prelude_python_sdk.types.verification_management import (
+ SandboxAddPhoneNumberResponse,
+ SandboxListPhoneNumbersResponse,
+ SandboxDeletePhoneNumberResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSandbox:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_add_phone_number(self, client: Prelude) -> None:
+ sandbox = client.verification_management.sandbox.add_phone_number(
+ attempt_code="123456",
+ phone_number="+30123456789",
+ )
+ assert_matches_type(SandboxAddPhoneNumberResponse, sandbox, path=["response"])
+
+ @parametrize
+ def test_raw_response_add_phone_number(self, client: Prelude) -> None:
+ response = client.verification_management.sandbox.with_raw_response.add_phone_number(
+ attempt_code="123456",
+ phone_number="+30123456789",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ sandbox = response.parse()
+ assert_matches_type(SandboxAddPhoneNumberResponse, sandbox, path=["response"])
+
+ @parametrize
+ def test_streaming_response_add_phone_number(self, client: Prelude) -> None:
+ with client.verification_management.sandbox.with_streaming_response.add_phone_number(
+ attempt_code="123456",
+ phone_number="+30123456789",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ sandbox = response.parse()
+ assert_matches_type(SandboxAddPhoneNumberResponse, sandbox, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_delete_phone_number(self, client: Prelude) -> None:
+ sandbox = client.verification_management.sandbox.delete_phone_number(
+ "+12065550100",
+ )
+ assert_matches_type(SandboxDeletePhoneNumberResponse, sandbox, path=["response"])
+
+ @parametrize
+ def test_raw_response_delete_phone_number(self, client: Prelude) -> None:
+ response = client.verification_management.sandbox.with_raw_response.delete_phone_number(
+ "+12065550100",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ sandbox = response.parse()
+ assert_matches_type(SandboxDeletePhoneNumberResponse, sandbox, path=["response"])
+
+ @parametrize
+ def test_streaming_response_delete_phone_number(self, client: Prelude) -> None:
+ with client.verification_management.sandbox.with_streaming_response.delete_phone_number(
+ "+12065550100",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ sandbox = response.parse()
+ assert_matches_type(SandboxDeletePhoneNumberResponse, sandbox, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_delete_phone_number(self, client: Prelude) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `phone_number` but received ''"):
+ client.verification_management.sandbox.with_raw_response.delete_phone_number(
+ "",
+ )
+
+ @parametrize
+ def test_method_list_phone_numbers(self, client: Prelude) -> None:
+ sandbox = client.verification_management.sandbox.list_phone_numbers()
+ assert_matches_type(SandboxListPhoneNumbersResponse, sandbox, path=["response"])
+
+ @parametrize
+ def test_raw_response_list_phone_numbers(self, client: Prelude) -> None:
+ response = client.verification_management.sandbox.with_raw_response.list_phone_numbers()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ sandbox = response.parse()
+ assert_matches_type(SandboxListPhoneNumbersResponse, sandbox, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list_phone_numbers(self, client: Prelude) -> None:
+ with client.verification_management.sandbox.with_streaming_response.list_phone_numbers() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ sandbox = response.parse()
+ assert_matches_type(SandboxListPhoneNumbersResponse, sandbox, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncSandbox:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_add_phone_number(self, async_client: AsyncPrelude) -> None:
+ sandbox = await async_client.verification_management.sandbox.add_phone_number(
+ attempt_code="123456",
+ phone_number="+30123456789",
+ )
+ assert_matches_type(SandboxAddPhoneNumberResponse, sandbox, path=["response"])
+
+ @parametrize
+ async def test_raw_response_add_phone_number(self, async_client: AsyncPrelude) -> None:
+ response = await async_client.verification_management.sandbox.with_raw_response.add_phone_number(
+ attempt_code="123456",
+ phone_number="+30123456789",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ sandbox = await response.parse()
+ assert_matches_type(SandboxAddPhoneNumberResponse, sandbox, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_add_phone_number(self, async_client: AsyncPrelude) -> None:
+ async with async_client.verification_management.sandbox.with_streaming_response.add_phone_number(
+ attempt_code="123456",
+ phone_number="+30123456789",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ sandbox = await response.parse()
+ assert_matches_type(SandboxAddPhoneNumberResponse, sandbox, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_delete_phone_number(self, async_client: AsyncPrelude) -> None:
+ sandbox = await async_client.verification_management.sandbox.delete_phone_number(
+ "+12065550100",
+ )
+ assert_matches_type(SandboxDeletePhoneNumberResponse, sandbox, path=["response"])
+
+ @parametrize
+ async def test_raw_response_delete_phone_number(self, async_client: AsyncPrelude) -> None:
+ response = await async_client.verification_management.sandbox.with_raw_response.delete_phone_number(
+ "+12065550100",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ sandbox = await response.parse()
+ assert_matches_type(SandboxDeletePhoneNumberResponse, sandbox, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_delete_phone_number(self, async_client: AsyncPrelude) -> None:
+ async with async_client.verification_management.sandbox.with_streaming_response.delete_phone_number(
+ "+12065550100",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ sandbox = await response.parse()
+ assert_matches_type(SandboxDeletePhoneNumberResponse, sandbox, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_delete_phone_number(self, async_client: AsyncPrelude) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `phone_number` but received ''"):
+ await async_client.verification_management.sandbox.with_raw_response.delete_phone_number(
+ "",
+ )
+
+ @parametrize
+ async def test_method_list_phone_numbers(self, async_client: AsyncPrelude) -> None:
+ sandbox = await async_client.verification_management.sandbox.list_phone_numbers()
+ assert_matches_type(SandboxListPhoneNumbersResponse, sandbox, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list_phone_numbers(self, async_client: AsyncPrelude) -> None:
+ response = await async_client.verification_management.sandbox.with_raw_response.list_phone_numbers()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ sandbox = await response.parse()
+ assert_matches_type(SandboxListPhoneNumbersResponse, sandbox, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list_phone_numbers(self, async_client: AsyncPrelude) -> None:
+ async with (
+ async_client.verification_management.sandbox.with_streaming_response.list_phone_numbers()
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ sandbox = await response.parse()
+ assert_matches_type(SandboxListPhoneNumbersResponse, sandbox, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
From 8d20fd9f2a7fcceb722a0c15fd42eef7035036f9 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 18 Sep 2026 15:48:50 +0000
Subject: [PATCH 2/2] release: 0.15.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 8 ++++++++
pyproject.toml | 2 +-
src/prelude_python_sdk/_version.py | 2 +-
4 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index a26ebfc1..8f3e0a49 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.14.0"
+ ".": "0.15.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4ff5333..04acfe8c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## 0.15.0 (2026-09-18)
+
+Full Changelog: [v0.14.0...v0.15.0](https://github.com/prelude-so/python-sdk/compare/v0.14.0...v0.15.0)
+
+### Features
+
+* **api:** manual updates ([6f8fe45](https://github.com/prelude-so/python-sdk/commit/6f8fe45cb794e6283855863693c12143ec2b4871))
+
## 0.14.0 (2026-09-15)
Full Changelog: [v0.13.0...v0.14.0](https://github.com/prelude-so/python-sdk/compare/v0.13.0...v0.14.0)
diff --git a/pyproject.toml b/pyproject.toml
index 2bdc9df6..090aafbf 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "prelude-python-sdk"
-version = "0.14.0"
+version = "0.15.0"
description = "The official Python library for the Prelude API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/prelude_python_sdk/_version.py b/src/prelude_python_sdk/_version.py
index 2aab5a9c..888f88cf 100644
--- a/src/prelude_python_sdk/_version.py
+++ b/src/prelude_python_sdk/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "prelude_python_sdk"
-__version__ = "0.14.0" # x-release-please-version
+__version__ = "0.15.0" # x-release-please-version