From 0efc28f63ebca1a4557d55311bf0cf3c3b89d8ac Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 18 Sep 2026 17:27:41 +0000 Subject: [PATCH 1/3] feat(api): api update --- .stats.yml | 4 +- src/Services/WatchRawService.php | 2 +- src/Services/WatchService.php | 2 +- .../Phone/History/HistoryGetResponse.php | 66 +++++++++++-------- .../HistoryGetResponse/BlockReason.php | 34 +++++++--- .../History/HistoryGetResponse/Status.php | 2 +- .../HistoryListResponse/Verification.php | 4 +- .../Verification/Status.php | 2 +- src/Verification/VerificationNewResponse.php | 66 ++++++++++++------- .../VerificationNewResponse/Reason.php | 4 +- .../VerificationNewResponse/RiskFactor.php | 34 +++++++--- src/Watch/WatchEvaluateParams.php | 2 +- src/Watch/WatchEvaluateResponse/Recipe.php | 4 +- .../WatchEvaluateResponse/Recipe/Rule.php | 56 +++++++++++++++- .../Recipe/Rule/Outcome.php | 3 + .../Recipe/Rule/Type.php | 17 +++++ src/Watch/WatchPredictResponse.php | 62 ++++++++++------- src/Watch/WatchPredictResponse/RiskFactor.php | 34 +++++++--- 18 files changed, 285 insertions(+), 113 deletions(-) create mode 100644 src/Watch/WatchEvaluateResponse/Recipe/Rule/Type.php diff --git a/.stats.yml b/.stats.yml index cce5f55..9e7e8a5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 27 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/prelude/prelude-33b70f86404b99046db5c61d3142b149dd7dec7eefe5e62ff3b397c7274aa5dc.yml -openapi_spec_hash: fb72aba38ec55a3b6b5162c9f5b4228d +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/prelude/prelude-0594c397de9595800d24337cbb03d0d1b7a1af7c0596890f5cf860662af1fd3b.yml +openapi_spec_hash: abc172473948cdc82c6baa4462fad2d9 config_hash: 707d65d2f456a2b5ba5b2c565b60128a diff --git a/src/Services/WatchRawService.php b/src/Services/WatchRawService.php index 74691c4..d651484 100644 --- a/src/Services/WatchRawService.php +++ b/src/Services/WatchRawService.php @@ -44,7 +44,7 @@ public function __construct(private Client $client) {} /** * @api * - * **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. + * **Beta.** The request and response shapes may still change. Talk to us before you build against it. Flows, recipes and rules are authored through the Watch Management API, or configured by Prelude on your behalf. * * 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. * diff --git a/src/Services/WatchService.php b/src/Services/WatchService.php index 3c9bd11..5433b09 100644 --- a/src/Services/WatchService.php +++ b/src/Services/WatchService.php @@ -47,7 +47,7 @@ public function __construct(private Client $client) /** * @api * - * **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. + * **Beta.** The request and response shapes may still change. Talk to us before you build against it. Flows, recipes and rules are authored through the Watch Management API, or configured by Prelude on your behalf. * * 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. * diff --git a/src/Verification/Phone/History/HistoryGetResponse.php b/src/Verification/Phone/History/HistoryGetResponse.php index 22875ab..84b4cd5 100644 --- a/src/Verification/Phone/History/HistoryGetResponse.php +++ b/src/Verification/Phone/History/HistoryGetResponse.php @@ -83,7 +83,7 @@ final class HistoryGetResponse implements BaseModel * * `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. + * * `expired_signals` - The SDK signals were collected too long before the request to still attest to it. * * `shadowed` - The anti-fraud system flagged the verification without blocking it. * * @var value-of $status @@ -98,19 +98,26 @@ final class HistoryGetResponse implements BaseModel public ?string $appVersion; /** - * 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. + * Why the anti-fraud system blocked the verification. Empty unless it did. These are the same labels the Verify and Watch APIs serve as `risk_factors`. + * * `automation_signature` - The request appears to come from an automated client rather than a person. + * * `carrier_not_permitted` - The destination carrier is one this account does not accept traffic for. + * * `client_fingerprint_mismatch` - The client does not appear to be the platform it identifies itself as. + * * `custom_policy` - A rule configured for your account matched this request. + * * `device_emulator` - The request appears to come from an emulator rather than a physical device. + * * `device_not_permitted` - The device platform is one your account blocks. + * * `device_reuse` - One device is driving verifications for an unusual number of phone numbers. + * * `expired_signals` - The SDK signals were collected too long before the request to still attest to it. + * * `fraud_database` - The phone number is flagged in one or more of the fraud databases Prelude consults. + * * `invalid_signature` - The SDK signature did not verify, so the request cannot be attributed to the device it claims to come from. + * * `ip_concentration` - The request shares its origin with an unusual volume of other verifications. + * * `ip_reputation` - The originating IP address is not trusted. + * * `location_mismatch` - The network location and the phone number's country are inconsistent. * * `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. + * * `number_range_abuse` - The phone number belongs to a range currently associated with abuse. + * * `poor_conversion_history` - Traffic resembling this request rarely completes a verification. + * * `proxy_network` - The request did not arrive over the subscriber's own access network. + * * `repeated_attempts` - The phone number exceeded the allowed number of verification attempts in a short period. + * * `temporary_phone_number` - The phone number belongs to a disposable or short-lived numbering service. * * @var list>|null $blockReasons */ @@ -354,7 +361,7 @@ public function withPhoneNumber(string $phoneNumber): self * * `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. + * * `expired_signals` - The SDK signals were collected too long before the request to still attest to it. * * `shadowed` - The anti-fraud system flagged the verification without blocking it. * * @param Status|value-of $status @@ -379,19 +386,26 @@ public function withAppVersion(string $appVersion): self } /** - * 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. + * Why the anti-fraud system blocked the verification. Empty unless it did. These are the same labels the Verify and Watch APIs serve as `risk_factors`. + * * `automation_signature` - The request appears to come from an automated client rather than a person. + * * `carrier_not_permitted` - The destination carrier is one this account does not accept traffic for. + * * `client_fingerprint_mismatch` - The client does not appear to be the platform it identifies itself as. + * * `custom_policy` - A rule configured for your account matched this request. + * * `device_emulator` - The request appears to come from an emulator rather than a physical device. + * * `device_not_permitted` - The device platform is one your account blocks. + * * `device_reuse` - One device is driving verifications for an unusual number of phone numbers. + * * `expired_signals` - The SDK signals were collected too long before the request to still attest to it. + * * `fraud_database` - The phone number is flagged in one or more of the fraud databases Prelude consults. + * * `invalid_signature` - The SDK signature did not verify, so the request cannot be attributed to the device it claims to come from. + * * `ip_concentration` - The request shares its origin with an unusual volume of other verifications. + * * `ip_reputation` - The originating IP address is not trusted. + * * `location_mismatch` - The network location and the phone number's country are inconsistent. * * `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. + * * `number_range_abuse` - The phone number belongs to a range currently associated with abuse. + * * `poor_conversion_history` - Traffic resembling this request rarely completes a verification. + * * `proxy_network` - The request did not arrive over the subscriber's own access network. + * * `repeated_attempts` - The phone number exceeded the allowed number of verification attempts in a short period. + * * `temporary_phone_number` - The phone number belongs to a disposable or short-lived numbering service. * * @param list> $blockReasons */ diff --git a/src/Verification/Phone/History/HistoryGetResponse/BlockReason.php b/src/Verification/Phone/History/HistoryGetResponse/BlockReason.php index 28391ce..0b6e036 100644 --- a/src/Verification/Phone/History/HistoryGetResponse/BlockReason.php +++ b/src/Verification/Phone/History/HistoryGetResponse/BlockReason.php @@ -6,27 +6,41 @@ enum BlockReason: string { - case BEHAVIORAL_PATTERN = 'behavioral_pattern'; + case AUTOMATION_SIGNATURE = 'automation_signature'; - case DEVICE_ATTRIBUTE = 'device_attribute'; + case CARRIER_NOT_PERMITTED = 'carrier_not_permitted'; + + case CLIENT_FINGERPRINT_MISMATCH = 'client_fingerprint_mismatch'; + + case CUSTOM_POLICY = 'custom_policy'; + + case DEVICE_EMULATOR = 'device_emulator'; + + case DEVICE_NOT_PERMITTED = 'device_not_permitted'; + + case DEVICE_REUSE = 'device_reuse'; + + case EXPIRED_SIGNALS = 'expired_signals'; case FRAUD_DATABASE = 'fraud_database'; - case LOCATION_DISCREPANCY = 'location_discrepancy'; + case INVALID_SIGNATURE = 'invalid_signature'; - case MISSING_SIGNALS = 'missing_signals'; + case IP_CONCENTRATION = 'ip_concentration'; - case NETWORK_FINGERPRINT = 'network_fingerprint'; + case IP_REPUTATION = 'ip_reputation'; - case POOR_CONVERSION_HISTORY = 'poor_conversion_history'; + case LOCATION_MISMATCH = 'location_mismatch'; - case PREFIX_CONCENTRATION = 'prefix_concentration'; + case MISSING_SIGNALS = 'missing_signals'; - case REPEATED_NUMBER = 'repeated_number'; + case NUMBER_RANGE_ABUSE = 'number_range_abuse'; + + case POOR_CONVERSION_HISTORY = 'poor_conversion_history'; - case SUSPECTED_REQUEST_TAMPERING = 'suspected_request_tampering'; + case PROXY_NETWORK = 'proxy_network'; - case SUSPICIOUS_IP_ADDRESS = 'suspicious_ip_address'; + case REPEATED_ATTEMPTS = 'repeated_attempts'; case TEMPORARY_PHONE_NUMBER = 'temporary_phone_number'; } diff --git a/src/Verification/Phone/History/HistoryGetResponse/Status.php b/src/Verification/Phone/History/HistoryGetResponse/Status.php index d457e45..2151e10 100644 --- a/src/Verification/Phone/History/HistoryGetResponse/Status.php +++ b/src/Verification/Phone/History/HistoryGetResponse/Status.php @@ -16,7 +16,7 @@ * * `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. + * * `expired_signals` - The SDK signals were collected too long before the request to still attest to it. * * `shadowed` - The anti-fraud system flagged the verification without blocking it. */ enum Status: string diff --git a/src/Verification/Phone/History/HistoryListResponse/Verification.php b/src/Verification/Phone/History/HistoryListResponse/Verification.php index c772962..0e115df 100644 --- a/src/Verification/Phone/History/HistoryListResponse/Verification.php +++ b/src/Verification/Phone/History/HistoryListResponse/Verification.php @@ -82,7 +82,7 @@ final class Verification implements BaseModel * * `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. + * * `expired_signals` - The SDK signals were collected too long before the request to still attest to it. * * `shadowed` - The anti-fraud system flagged the verification without blocking it. * * @var value-of $status @@ -275,7 +275,7 @@ public function withPhoneNumber(string $phoneNumber): self * * `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. + * * `expired_signals` - The SDK signals were collected too long before the request to still attest to it. * * `shadowed` - The anti-fraud system flagged the verification without blocking it. * * @param Status|value-of $status diff --git a/src/Verification/Phone/History/HistoryListResponse/Verification/Status.php b/src/Verification/Phone/History/HistoryListResponse/Verification/Status.php index 6e95f69..014966d 100644 --- a/src/Verification/Phone/History/HistoryListResponse/Verification/Status.php +++ b/src/Verification/Phone/History/HistoryListResponse/Verification/Status.php @@ -16,7 +16,7 @@ * * `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. + * * `expired_signals` - The SDK signals were collected too long before the request to still attest to it. * * `shadowed` - The anti-fraud system flagged the verification without blocking it. */ enum Status: string diff --git a/src/Verification/VerificationNewResponse.php b/src/Verification/VerificationNewResponse.php index 30a401c..4fe77e0 100644 --- a/src/Verification/VerificationNewResponse.php +++ b/src/Verification/VerificationNewResponse.php @@ -85,8 +85,8 @@ final class VerificationNewResponse implements BaseModel * * `in_block_list` - The phone number is part of the configured block list. * * `invalid_phone_line` - The phone number is not a valid line number (e.g. landline). * * `invalid_phone_number` - The phone number is not a valid phone number (e.g. unallocated range). - * * `invalid_signature` - The signature of the SDK signals is invalid. - * * `repeated_attempts` - The phone number has made too many verification attempts. + * * `invalid_signature` - The SDK signature did not verify, so the request cannot be attributed to the device it claims to come from. + * * `repeated_attempts` - The phone number exceeded the allowed number of verification attempts in a short period. * * `suspicious` - The verification attempt was deemed suspicious by the anti-fraud system. * * @var value-of|null $reason @@ -99,16 +99,25 @@ final class VerificationNewResponse implements BaseModel /** * The risk factors that contributed to the verification being blocked. Only present when status is "blocked" or "shadow_blocked" and the anti-fraud system detected specific risk signals. - * * `behavioral_pattern` - The phone number past behavior during verification flows exhibits suspicious patterns. - * * `device_attribute` - The device exhibits characteristics associated with suspicious activity patterns. - * * `fraud_database` - The phone number has been flagged as suspicious in one or more of our fraud databases. - * * `location_discrepancy` - The phone number prefix and IP address discrepancy indicates potential fraud. - * * `network_fingerprint` - The network connection exhibits characteristics associated with suspicious activity patterns. - * * `poor_conversion_history` - The phone number has a history of poorly converting to a verified phone number. - * * `prefix_concentration` - The phone number is part of a range known to be associated with suspicious activity patterns. - * * `suspected_request_tampering` - The SDK signature is invalid and the request is considered to be tampered with. - * * `suspicious_ip_address` - The IP address is deemed to be associated with suspicious activity patterns. - * * `temporary_phone_number` - The phone number is known to be a temporary or disposable number. + * * `automation_signature` - The request appears to come from an automated client rather than a person. + * * `carrier_not_permitted` - The destination carrier is one this account does not accept traffic for. + * * `client_fingerprint_mismatch` - The client does not appear to be the platform it identifies itself as. + * * `custom_policy` - A rule configured for your account matched this request. + * * `device_emulator` - The request appears to come from an emulator rather than a physical device. + * * `device_not_permitted` - The device platform is one your account blocks. + * * `device_reuse` - One device is driving verifications for an unusual number of phone numbers. + * * `expired_signals` - The SDK signals were collected too long before the request to still attest to it. + * * `fraud_database` - The phone number is flagged in one or more of the fraud databases Prelude consults. + * * `invalid_signature` - The SDK signature did not verify, so the request cannot be attributed to the device it claims to come from. + * * `ip_concentration` - The request shares its origin with an unusual volume of other verifications. + * * `ip_reputation` - The originating IP address is not trusted. + * * `location_mismatch` - The network location and the phone number's country are inconsistent. + * * `missing_signals` - The verification expected Prelude SDK signals and none arrived. + * * `number_range_abuse` - The phone number belongs to a range currently associated with abuse. + * * `poor_conversion_history` - Traffic resembling this request rarely completes a verification. + * * `proxy_network` - The request did not arrive over the subscriber's own access network. + * * `repeated_attempts` - The phone number exceeded the allowed number of verification attempts in a short period. + * * `temporary_phone_number` - The phone number belongs to a disposable or short-lived numbering service. * * @var list>|null $riskFactors */ @@ -255,8 +264,8 @@ public function withMetadata(Metadata|array $metadata): self * * `in_block_list` - The phone number is part of the configured block list. * * `invalid_phone_line` - The phone number is not a valid line number (e.g. landline). * * `invalid_phone_number` - The phone number is not a valid phone number (e.g. unallocated range). - * * `invalid_signature` - The signature of the SDK signals is invalid. - * * `repeated_attempts` - The phone number has made too many verification attempts. + * * `invalid_signature` - The SDK signature did not verify, so the request cannot be attributed to the device it claims to come from. + * * `repeated_attempts` - The phone number exceeded the allowed number of verification attempts in a short period. * * `suspicious` - The verification attempt was deemed suspicious by the anti-fraud system. * * @param Reason|value-of $reason @@ -279,16 +288,25 @@ public function withRequestID(string $requestID): self /** * The risk factors that contributed to the verification being blocked. Only present when status is "blocked" or "shadow_blocked" and the anti-fraud system detected specific risk signals. - * * `behavioral_pattern` - The phone number past behavior during verification flows exhibits suspicious patterns. - * * `device_attribute` - The device exhibits characteristics associated with suspicious activity patterns. - * * `fraud_database` - The phone number has been flagged as suspicious in one or more of our fraud databases. - * * `location_discrepancy` - The phone number prefix and IP address discrepancy indicates potential fraud. - * * `network_fingerprint` - The network connection exhibits characteristics associated with suspicious activity patterns. - * * `poor_conversion_history` - The phone number has a history of poorly converting to a verified phone number. - * * `prefix_concentration` - The phone number is part of a range known to be associated with suspicious activity patterns. - * * `suspected_request_tampering` - The SDK signature is invalid and the request is considered to be tampered with. - * * `suspicious_ip_address` - The IP address is deemed to be associated with suspicious activity patterns. - * * `temporary_phone_number` - The phone number is known to be a temporary or disposable number. + * * `automation_signature` - The request appears to come from an automated client rather than a person. + * * `carrier_not_permitted` - The destination carrier is one this account does not accept traffic for. + * * `client_fingerprint_mismatch` - The client does not appear to be the platform it identifies itself as. + * * `custom_policy` - A rule configured for your account matched this request. + * * `device_emulator` - The request appears to come from an emulator rather than a physical device. + * * `device_not_permitted` - The device platform is one your account blocks. + * * `device_reuse` - One device is driving verifications for an unusual number of phone numbers. + * * `expired_signals` - The SDK signals were collected too long before the request to still attest to it. + * * `fraud_database` - The phone number is flagged in one or more of the fraud databases Prelude consults. + * * `invalid_signature` - The SDK signature did not verify, so the request cannot be attributed to the device it claims to come from. + * * `ip_concentration` - The request shares its origin with an unusual volume of other verifications. + * * `ip_reputation` - The originating IP address is not trusted. + * * `location_mismatch` - The network location and the phone number's country are inconsistent. + * * `missing_signals` - The verification expected Prelude SDK signals and none arrived. + * * `number_range_abuse` - The phone number belongs to a range currently associated with abuse. + * * `poor_conversion_history` - Traffic resembling this request rarely completes a verification. + * * `proxy_network` - The request did not arrive over the subscriber's own access network. + * * `repeated_attempts` - The phone number exceeded the allowed number of verification attempts in a short period. + * * `temporary_phone_number` - The phone number belongs to a disposable or short-lived numbering service. * * @param list> $riskFactors */ diff --git a/src/Verification/VerificationNewResponse/Reason.php b/src/Verification/VerificationNewResponse/Reason.php index c83b649..00cd051 100644 --- a/src/Verification/VerificationNewResponse/Reason.php +++ b/src/Verification/VerificationNewResponse/Reason.php @@ -11,8 +11,8 @@ * * `in_block_list` - The phone number is part of the configured block list. * * `invalid_phone_line` - The phone number is not a valid line number (e.g. landline). * * `invalid_phone_number` - The phone number is not a valid phone number (e.g. unallocated range). - * * `invalid_signature` - The signature of the SDK signals is invalid. - * * `repeated_attempts` - The phone number has made too many verification attempts. + * * `invalid_signature` - The SDK signature did not verify, so the request cannot be attributed to the device it claims to come from. + * * `repeated_attempts` - The phone number exceeded the allowed number of verification attempts in a short period. * * `suspicious` - The verification attempt was deemed suspicious by the anti-fraud system. */ enum Reason: string diff --git a/src/Verification/VerificationNewResponse/RiskFactor.php b/src/Verification/VerificationNewResponse/RiskFactor.php index 7599b17..4e87395 100644 --- a/src/Verification/VerificationNewResponse/RiskFactor.php +++ b/src/Verification/VerificationNewResponse/RiskFactor.php @@ -6,23 +6,41 @@ enum RiskFactor: string { - case BEHAVIORAL_PATTERN = 'behavioral_pattern'; + case AUTOMATION_SIGNATURE = 'automation_signature'; - case DEVICE_ATTRIBUTE = 'device_attribute'; + case CARRIER_NOT_PERMITTED = 'carrier_not_permitted'; + + case CLIENT_FINGERPRINT_MISMATCH = 'client_fingerprint_mismatch'; + + case CUSTOM_POLICY = 'custom_policy'; + + case DEVICE_EMULATOR = 'device_emulator'; + + case DEVICE_NOT_PERMITTED = 'device_not_permitted'; + + case DEVICE_REUSE = 'device_reuse'; + + case EXPIRED_SIGNALS = 'expired_signals'; case FRAUD_DATABASE = 'fraud_database'; - case LOCATION_DISCREPANCY = 'location_discrepancy'; + case INVALID_SIGNATURE = 'invalid_signature'; - case NETWORK_FINGERPRINT = 'network_fingerprint'; + case IP_CONCENTRATION = 'ip_concentration'; - case POOR_CONVERSION_HISTORY = 'poor_conversion_history'; + case IP_REPUTATION = 'ip_reputation'; + + case LOCATION_MISMATCH = 'location_mismatch'; + + case MISSING_SIGNALS = 'missing_signals'; - case PREFIX_CONCENTRATION = 'prefix_concentration'; + case NUMBER_RANGE_ABUSE = 'number_range_abuse'; + + case POOR_CONVERSION_HISTORY = 'poor_conversion_history'; - case SUSPECTED_REQUEST_TAMPERING = 'suspected_request_tampering'; + case PROXY_NETWORK = 'proxy_network'; - case SUSPICIOUS_IP_ADDRESS = 'suspicious_ip_address'; + case REPEATED_ATTEMPTS = 'repeated_attempts'; case TEMPORARY_PHONE_NUMBER = 'temporary_phone_number'; } diff --git a/src/Watch/WatchEvaluateParams.php b/src/Watch/WatchEvaluateParams.php index 25f81e3..734f682 100644 --- a/src/Watch/WatchEvaluateParams.php +++ b/src/Watch/WatchEvaluateParams.php @@ -13,7 +13,7 @@ use Prelude\Target; /** - * **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. + * **Beta.** The request and response shapes may still change. Talk to us before you build against it. Flows, recipes and rules are authored through the Watch Management API, or configured by Prelude on your behalf. * * 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. * diff --git a/src/Watch/WatchEvaluateResponse/Recipe.php b/src/Watch/WatchEvaluateResponse/Recipe.php index 111d429..6f254ff 100644 --- a/src/Watch/WatchEvaluateResponse/Recipe.php +++ b/src/Watch/WatchEvaluateResponse/Recipe.php @@ -42,7 +42,7 @@ final class Recipe implements BaseModel public string $recipeID; /** - * 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. + * 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. The exception is a recipe whose verdict a preempting rule has already determined, where a rule that could no longer change it may report `SKIPPED` instead. * * @var list $rules */ @@ -161,7 +161,7 @@ public function withRecipeID(string $recipeID): self } /** - * 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. + * 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. The exception is a recipe whose verdict a preempting rule has already determined, where a rule that could no longer change it may report `SKIPPED` instead. * * @param list $rules */ diff --git a/src/Watch/WatchEvaluateResponse/Recipe/Rule.php b/src/Watch/WatchEvaluateResponse/Recipe/Rule.php index ab2a2a7..9d9a88f 100644 --- a/src/Watch/WatchEvaluateResponse/Recipe/Rule.php +++ b/src/Watch/WatchEvaluateResponse/Recipe/Rule.php @@ -9,15 +9,18 @@ use Prelude\Core\Concerns\SdkModel; use Prelude\Core\Contracts\BaseModel; use Prelude\Watch\WatchEvaluateResponse\Recipe\Rule\Outcome; +use Prelude\Watch\WatchEvaluateResponse\Recipe\Rule\Type; /** * @phpstan-type RuleShape = array{ * outcome: Outcome|value-of, * ruleID: string, + * type: Type|value-of, * weight: int, * blockedBy?: string|null, * name?: string|null, * unavailable?: bool|null, + * versionID?: string|null, * } */ final class Rule implements BaseModel @@ -30,6 +33,7 @@ final class Rule implements BaseModel * * `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. + * * `SKIPPED` - The rule was not run, because another rule had already determined the recipe's verdict — see `determined_by`. Nothing was missing and nothing failed, so `partial_evidence` is not set: `determined_by` is what accounts for the recipe's score resting on fewer rules. * * @var value-of $outcome */ @@ -42,6 +46,16 @@ final class Rule implements BaseModel #[Required('rule_id')] public string $ruleID; + /** + * Who authored the rule, which is what says how much of the rest of this result you get. + * * `MANAGED` - Prelude-owned, shared with customers: `name` and `version_id` are omitted, and `blocked_by` reports only `missing_data`. + * * `CUSTOM` - Yours: every field is returned. + * + * @var value-of $type + */ + #[Required(enum: Type::class)] + public string $type; + /** * What this rule contributes to the recipe's score when it triggers. */ @@ -68,18 +82,24 @@ final class Rule implements BaseModel #[Optional] public ?bool $unavailable; + /** + * The version of the rule that scored — the one this recipe is pinned to, or the version current at evaluation time when it is not pinned. Present for a rule you authored, and omitted for a Prelude-managed one. + */ + #[Optional('version_id')] + public ?string $versionID; + /** * `new Rule()` is missing required properties by the API. * * To enforce required parameters use * ``` - * Rule::with(outcome: ..., ruleID: ..., weight: ...) + * Rule::with(outcome: ..., ruleID: ..., type: ..., weight: ...) * ``` * * Otherwise ensure the following setters are called * * ``` - * (new Rule)->withOutcome(...)->withRuleID(...)->withWeight(...) + * (new Rule)->withOutcome(...)->withRuleID(...)->withType(...)->withWeight(...) * ``` */ public function __construct() @@ -93,24 +113,29 @@ public function __construct() * You must use named parameters to construct any parameters with a default value. * * @param Outcome|value-of $outcome + * @param Type|value-of $type */ public static function with( Outcome|string $outcome, string $ruleID, + Type|string $type, int $weight, ?string $blockedBy = null, ?string $name = null, ?bool $unavailable = null, + ?string $versionID = null, ): self { $self = new self; $self['outcome'] = $outcome; $self['ruleID'] = $ruleID; + $self['type'] = $type; $self['weight'] = $weight; null !== $blockedBy && $self['blockedBy'] = $blockedBy; null !== $name && $self['name'] = $name; null !== $unavailable && $self['unavailable'] = $unavailable; + null !== $versionID && $self['versionID'] = $versionID; return $self; } @@ -120,6 +145,7 @@ public static function with( * * `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. + * * `SKIPPED` - The rule was not run, because another rule had already determined the recipe's verdict — see `determined_by`. Nothing was missing and nothing failed, so `partial_evidence` is not set: `determined_by` is what accounts for the recipe's score resting on fewer rules. * * @param Outcome|value-of $outcome */ @@ -142,6 +168,21 @@ public function withRuleID(string $ruleID): self return $self; } + /** + * Who authored the rule, which is what says how much of the rest of this result you get. + * * `MANAGED` - Prelude-owned, shared with customers: `name` and `version_id` are omitted, and `blocked_by` reports only `missing_data`. + * * `CUSTOM` - Yours: every field is returned. + * + * @param Type|value-of $type + */ + public function withType(Type|string $type): self + { + $self = clone $this; + $self['type'] = $type; + + return $self; + } + /** * What this rule contributes to the recipe's score when it triggers. */ @@ -187,4 +228,15 @@ public function withUnavailable(bool $unavailable): self return $self; } + + /** + * The version of the rule that scored — the one this recipe is pinned to, or the version current at evaluation time when it is not pinned. Present for a rule you authored, and omitted for a Prelude-managed one. + */ + public function withVersionID(string $versionID): self + { + $self = clone $this; + $self['versionID'] = $versionID; + + return $self; + } } diff --git a/src/Watch/WatchEvaluateResponse/Recipe/Rule/Outcome.php b/src/Watch/WatchEvaluateResponse/Recipe/Rule/Outcome.php index 152a930..233a721 100644 --- a/src/Watch/WatchEvaluateResponse/Recipe/Rule/Outcome.php +++ b/src/Watch/WatchEvaluateResponse/Recipe/Rule/Outcome.php @@ -9,6 +9,7 @@ * * `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. + * * `SKIPPED` - The rule was not run, because another rule had already determined the recipe's verdict — see `determined_by`. Nothing was missing and nothing failed, so `partial_evidence` is not set: `determined_by` is what accounts for the recipe's score resting on fewer rules. */ enum Outcome: string { @@ -17,4 +18,6 @@ enum Outcome: string case NOT_TRIGGERED = 'NOT_TRIGGERED'; case NOT_EVALUATED = 'NOT_EVALUATED'; + + case SKIPPED = 'SKIPPED'; } diff --git a/src/Watch/WatchEvaluateResponse/Recipe/Rule/Type.php b/src/Watch/WatchEvaluateResponse/Recipe/Rule/Type.php new file mode 100644 index 0000000..b9ec1aa --- /dev/null +++ b/src/Watch/WatchEvaluateResponse/Recipe/Rule/Type.php @@ -0,0 +1,17 @@ +>|null $riskFactors */ @@ -144,17 +153,26 @@ public function withRequestID(string $requestID): self /** * The risk factors that contributed to the suspicious prediction. Only present when prediction is "suspicious" and the anti-fraud system detected specific risk signals. - * * `account_risk_profile` - The target matches a risk profile derived from the outcomes reported on your own account, rather than from a signal shared across accounts. - * * `behavioral_pattern` - The phone number past behavior during verification flows exhibits suspicious patterns. - * * `device_attribute` - The device exhibits characteristics associated with suspicious activity patterns. - * * `fraud_database` - The phone number has been flagged as suspicious in one or more of our fraud databases. - * * `location_discrepancy` - The phone number prefix and IP address discrepancy indicates potential fraud. - * * `network_fingerprint` - The network connection exhibits characteristics associated with suspicious activity patterns. - * * `poor_conversion_history` - The phone number has a history of poorly converting to a verified phone number. - * * `prefix_concentration` - The phone number is part of a range known to be associated with suspicious activity patterns. - * * `suspected_request_tampering` - The SDK signature is invalid and the request is considered to be tampered with. - * * `suspicious_ip_address` - The IP address is deemed to be associated with suspicious activity patterns. - * * `temporary_phone_number` - The phone number is known to be a temporary or disposable number. + * * `account_risk_profile` - The request matches a risk profile derived from the outcomes reported on your own account. + * * `automation_signature` - The request appears to come from an automated client rather than a person. + * * `carrier_not_permitted` - The destination carrier is one this account does not accept traffic for. + * * `client_fingerprint_mismatch` - The client does not appear to be the platform it identifies itself as. + * * `custom_policy` - A rule configured for your account matched this request. + * * `device_emulator` - The request appears to come from an emulator rather than a physical device. + * * `device_not_permitted` - The device platform is one your account blocks. + * * `device_reuse` - One device is driving verifications for an unusual number of phone numbers. + * * `expired_signals` - The SDK signals were collected too long before the request to still attest to it. + * * `fraud_database` - The phone number is flagged in one or more of the fraud databases Prelude consults. + * * `invalid_signature` - The SDK signature did not verify, so the request cannot be attributed to the device it claims to come from. + * * `ip_concentration` - The request shares its origin with an unusual volume of other verifications. + * * `ip_reputation` - The originating IP address is not trusted. + * * `location_mismatch` - The network location and the phone number's country are inconsistent. + * * `missing_signals` - The verification expected Prelude SDK signals and none arrived. + * * `number_range_abuse` - The phone number belongs to a range currently associated with abuse. + * * `poor_conversion_history` - Traffic resembling this request rarely completes a verification. + * * `proxy_network` - The request did not arrive over the subscriber's own access network. + * * `repeated_attempts` - The phone number exceeded the allowed number of verification attempts in a short period. + * * `temporary_phone_number` - The phone number belongs to a disposable or short-lived numbering service. * * @param list> $riskFactors */ diff --git a/src/Watch/WatchPredictResponse/RiskFactor.php b/src/Watch/WatchPredictResponse/RiskFactor.php index 9deaa3f..6beb55f 100644 --- a/src/Watch/WatchPredictResponse/RiskFactor.php +++ b/src/Watch/WatchPredictResponse/RiskFactor.php @@ -8,23 +8,41 @@ enum RiskFactor: string { case ACCOUNT_RISK_PROFILE = 'account_risk_profile'; - case BEHAVIORAL_PATTERN = 'behavioral_pattern'; + case AUTOMATION_SIGNATURE = 'automation_signature'; - case DEVICE_ATTRIBUTE = 'device_attribute'; + case CARRIER_NOT_PERMITTED = 'carrier_not_permitted'; + + case CLIENT_FINGERPRINT_MISMATCH = 'client_fingerprint_mismatch'; + + case CUSTOM_POLICY = 'custom_policy'; + + case DEVICE_EMULATOR = 'device_emulator'; + + case DEVICE_NOT_PERMITTED = 'device_not_permitted'; + + case DEVICE_REUSE = 'device_reuse'; + + case EXPIRED_SIGNALS = 'expired_signals'; case FRAUD_DATABASE = 'fraud_database'; - case LOCATION_DISCREPANCY = 'location_discrepancy'; + case INVALID_SIGNATURE = 'invalid_signature'; - case NETWORK_FINGERPRINT = 'network_fingerprint'; + case IP_CONCENTRATION = 'ip_concentration'; - case POOR_CONVERSION_HISTORY = 'poor_conversion_history'; + case IP_REPUTATION = 'ip_reputation'; + + case LOCATION_MISMATCH = 'location_mismatch'; + + case MISSING_SIGNALS = 'missing_signals'; - case PREFIX_CONCENTRATION = 'prefix_concentration'; + case NUMBER_RANGE_ABUSE = 'number_range_abuse'; + + case POOR_CONVERSION_HISTORY = 'poor_conversion_history'; - case SUSPECTED_REQUEST_TAMPERING = 'suspected_request_tampering'; + case PROXY_NETWORK = 'proxy_network'; - case SUSPICIOUS_IP_ADDRESS = 'suspicious_ip_address'; + case REPEATED_ATTEMPTS = 'repeated_attempts'; case TEMPORARY_PHONE_NUMBER = 'temporary_phone_number'; } From ac98e874cd224405bd2847ef15336b17fe36601b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 18 Sep 2026 18:24:47 +0000 Subject: [PATCH 2/3] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9e7e8a5..73ca5fa 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 27 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/prelude/prelude-0594c397de9595800d24337cbb03d0d1b7a1af7c0596890f5cf860662af1fd3b.yml -openapi_spec_hash: abc172473948cdc82c6baa4462fad2d9 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/prelude/prelude-fdbaf5386743020c305990b67afae97f62c69478baafc39c3efbd296cb62770c.yml +openapi_spec_hash: 14d5b04b5dfa004a9cb0b1317e928abe config_hash: 707d65d2f456a2b5ba5b2c565b60128a From 37f2ea7b0b718d3c70e56d9c50a8838e054d22b4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 18 Sep 2026 18:25:10 +0000 Subject: [PATCH 3/3] release: 0.9.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ README.md | 2 +- src/Version.php | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6538ca9..6d78745 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.8.0" + ".": "0.9.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 174a9f3..5a900fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.9.0 (2026-09-18) + +Full Changelog: [v0.8.0...v0.9.0](https://github.com/prelude-so/php-sdk/compare/v0.8.0...v0.9.0) + +### Features + +* **api:** api update ([0efc28f](https://github.com/prelude-so/php-sdk/commit/0efc28f63ebca1a4557d55311bf0cf3c3b89d8ac)) + ## 0.8.0 (2026-09-18) Full Changelog: [v0.7.0...v0.8.0](https://github.com/prelude-so/php-sdk/compare/v0.7.0...v0.8.0) diff --git a/README.md b/README.md index 9bb389d..765e0fd 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The REST API documentation can be found on [docs.prelude.so](https://docs.prelud ``` -composer require "prelude-so/php-sdk 0.8.0" +composer require "prelude-so/php-sdk 0.9.0" ``` diff --git a/src/Version.php b/src/Version.php index f5b0c8e..fa0b4d1 100644 --- a/src/Version.php +++ b/src/Version.php @@ -5,5 +5,5 @@ namespace Prelude; // x-release-please-start-version -const VERSION = '0.8.0'; +const VERSION = '0.9.0'; // x-release-please-end