diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 26b1ce2..2b2b4fa 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.50.0" + ".": "0.51.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index ddb1fe8..ac9266e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 183 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-5d75bbdcac3a0b0a8b798c0b4ef0dacfbc64ad4a20605489dc46dda7c8d1d8d8.yml -openapi_spec_hash: bb242cfd8cc43354412164c2dceae89c -config_hash: 261e1b852ca7f8364e4a283a3edd350a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-be6c0709d91750e8a9ea4ac11c26c00ef39b0e09233beb454131452d10a99e95.yml +openapi_spec_hash: ba279d70955f6b4da3fcf63e92158406 +config_hash: f6a05e2edf2cd1af3f762b8ac759b9a7 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7617f51..363cf52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 0.51.0 (2026-09-16) + +Full Changelog: [v0.50.0...v0.51.0](https://github.com/zavudev/sdk-python/compare/v0.50.0...v0.51.0) + +### Features + +* **api:** api update ([04a0b6d](https://github.com/zavudev/sdk-python/commit/04a0b6dbf002cc4c92180a3ce84cd8c0992ca5d6)) +* **api:** api update ([0cf2295](https://github.com/zavudev/sdk-python/commit/0cf22951bce9cb1719aeac4690629d55aa1d3dab)) +* **api:** api update ([aab53c7](https://github.com/zavudev/sdk-python/commit/aab53c77cf90c954c9dd644424aa3dccc921480b)) +* **api:** api update ([b05594c](https://github.com/zavudev/sdk-python/commit/b05594c35e38a800ccd15ec1eeaa2e833b3ab5c6)) +* **api:** api update ([9a3148c](https://github.com/zavudev/sdk-python/commit/9a3148cf7bbf2c9ac183e9c2d90ec8f3c6f4da24)) +* **api:** api update ([77c03d9](https://github.com/zavudev/sdk-python/commit/77c03d988890df623ec37dad0c64f96d97ed32b2)) + ## 0.50.0 (2026-09-08) Full Changelog: [v0.49.1...v0.50.0](https://github.com/zavudev/sdk-python/compare/v0.49.1...v0.50.0) diff --git a/pyproject.toml b/pyproject.toml index fb774b6..a11da93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "zavudev" -version = "0.50.0" +version = "0.51.0" description = "The official Python library for the zavudev API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/zavudev/_version.py b/src/zavudev/_version.py index df55d3f..0666ce6 100644 --- a/src/zavudev/_version.py +++ b/src/zavudev/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "zavudev" -__version__ = "0.50.0" # x-release-please-version +__version__ = "0.51.0" # x-release-please-version diff --git a/src/zavudev/resources/addresses.py b/src/zavudev/resources/addresses.py index 0fade96..0b7fbe5 100644 --- a/src/zavudev/resources/addresses.py +++ b/src/zavudev/resources/addresses.py @@ -48,14 +48,14 @@ def create( self, *, country_code: str, + first_name: str, + last_name: str, locality: str, postal_code: str, street_address: str, administrative_area: str | Omit = omit, business_name: str | Omit = omit, extended_address: str | Omit = omit, - first_name: str | Omit = omit, - last_name: 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, @@ -63,12 +63,19 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AddressCreateResponse: - """Create a regulatory address for phone number purchases. - - Some countries require a - verified address before phone numbers can be activated. + """ + Create a regulatory address, to use as the value of an `address` requirement + when buying a phone number. It is registered for review when it is created, with + status `pending`. Args: + first_name: First name of the person the address is registered to. + + last_name: Last name of the person the address is registered to. + + business_name: Business name, when the address belongs to a business. Defaults to the person's + full name. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -82,14 +89,14 @@ def create( body=maybe_transform( { "country_code": country_code, + "first_name": first_name, + "last_name": last_name, "locality": locality, "postal_code": postal_code, "street_address": street_address, "administrative_area": administrative_area, "business_name": business_name, "extended_address": extended_address, - "first_name": first_name, - "last_name": last_name, }, address_create_params.AddressCreateParams, ), @@ -186,9 +193,11 @@ def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: - """Delete a regulatory address. + """Delete a regulatory address from this project. - Cannot delete addresses that are in use. + Any address can be deleted, + whatever its status. Phone numbers already purchased with it are not affected, + and neither is information already submitted for later purchases in its country. Args: extra_headers: Send extra headers @@ -235,14 +244,14 @@ async def create( self, *, country_code: str, + first_name: str, + last_name: str, locality: str, postal_code: str, street_address: str, administrative_area: str | Omit = omit, business_name: str | Omit = omit, extended_address: str | Omit = omit, - first_name: str | Omit = omit, - last_name: 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, @@ -250,12 +259,19 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AddressCreateResponse: - """Create a regulatory address for phone number purchases. - - Some countries require a - verified address before phone numbers can be activated. + """ + Create a regulatory address, to use as the value of an `address` requirement + when buying a phone number. It is registered for review when it is created, with + status `pending`. Args: + first_name: First name of the person the address is registered to. + + last_name: Last name of the person the address is registered to. + + business_name: Business name, when the address belongs to a business. Defaults to the person's + full name. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -269,14 +285,14 @@ async def create( body=await async_maybe_transform( { "country_code": country_code, + "first_name": first_name, + "last_name": last_name, "locality": locality, "postal_code": postal_code, "street_address": street_address, "administrative_area": administrative_area, "business_name": business_name, "extended_address": extended_address, - "first_name": first_name, - "last_name": last_name, }, address_create_params.AddressCreateParams, ), @@ -373,9 +389,11 @@ async def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: - """Delete a regulatory address. + """Delete a regulatory address from this project. - Cannot delete addresses that are in use. + Any address can be deleted, + whatever its status. Phone numbers already purchased with it are not affected, + and neither is information already submitted for later purchases in its country. Args: extra_headers: Send extra headers diff --git a/src/zavudev/resources/messages.py b/src/zavudev/resources/messages.py index 1e3c984..265ff49 100644 --- a/src/zavudev/resources/messages.py +++ b/src/zavudev/resources/messages.py @@ -258,9 +258,16 @@ def send( **Plan allowances and email billing:** - WhatsApp, Telegram, Instagram and Messenger share an allowance of 2,000 - messages per month on Free. Over it, sends return 429 with code - `a2p_limit_exceeded` and upgrade details; the counter resets on the 1st of - each month. Paid plans have no message caps + messages per month on Free. **It counts messages in both directions**: a + message a contact sends you consumes one unit exactly as a message you send + them does, so a project that has sent 300 and received 1,700 has used the + whole allowance. Messages you send from the WhatsApp Business App on your own + phone under coexistence are mirrored into your inbox but never counted, and + neither are failed sends. Over the allowance, sends return 429 with code + `a2p_limit_exceeded` and upgrade details, **and inbound messages on those + channels are refused as well**: not stored, not shown in the inbox, and no + `message.inbound` webhook, and not delivered later when the month resets. The + counter resets on the 1st of each month. Paid plans have no message caps - Email is billed from your prepaid balance in 1,000-message blocks: $0.40 per 1,000 transactional emails, $0.80 per 1,000 marketing (broadcast) emails. A block is charged when your monthly count crosses each 1,000 boundary, and at @@ -649,9 +656,16 @@ async def send( **Plan allowances and email billing:** - WhatsApp, Telegram, Instagram and Messenger share an allowance of 2,000 - messages per month on Free. Over it, sends return 429 with code - `a2p_limit_exceeded` and upgrade details; the counter resets on the 1st of - each month. Paid plans have no message caps + messages per month on Free. **It counts messages in both directions**: a + message a contact sends you consumes one unit exactly as a message you send + them does, so a project that has sent 300 and received 1,700 has used the + whole allowance. Messages you send from the WhatsApp Business App on your own + phone under coexistence are mirrored into your inbox but never counted, and + neither are failed sends. Over the allowance, sends return 429 with code + `a2p_limit_exceeded` and upgrade details, **and inbound messages on those + channels are refused as well**: not stored, not shown in the inbox, and no + `message.inbound` webhook, and not delivered later when the month resets. The + counter resets on the 1st of each month. Paid plans have no message caps - Email is billed from your prepaid balance in 1,000-message blocks: $0.40 per 1,000 transactional emails, $0.80 per 1,000 marketing (broadcast) emails. A block is charged when your monthly count crosses each 1,000 boundary, and at diff --git a/src/zavudev/resources/phone_numbers.py b/src/zavudev/resources/phone_numbers.py index cd0ee4a..d8a4329 100644 --- a/src/zavudev/resources/phone_numbers.py +++ b/src/zavudev/resources/phone_numbers.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional +from typing import Iterable, Optional import httpx @@ -111,7 +111,9 @@ def update( Args: name: Custom name for the phone number. Set to null to clear. - sender_id: Sender ID to assign the phone number to. Set to null to unassign. + sender_id: Sender ID to assign the phone number to. Set to null to unassign. A number under + regulatory review is recorded now and connected to the sender when approved; a + rejected number is refused. extra_headers: Send extra headers @@ -192,6 +194,8 @@ def purchase( *, phone_number: str, name: str | Omit = omit, + regulatory_requirements: Iterable[phone_number_purchase_params.RegulatoryRequirement] | Omit = omit, + type: PhoneNumberType | 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, @@ -202,17 +206,63 @@ def purchase( """Purchase an available phone number. Requires a paid plan: the Free plan cannot - purchase phone numbers and receives `402` with code `paid_plan_required`. Paid - plans include one US number at no charge. The included number is one per account - and is granted once: claiming it spends the benefit for good, so releasing that - number does not make another one free, and numbers the account already bought do - not consume it. + purchase phone numbers and receives `402` with code `paid_plan_required`. + + **The included number.** A paid plan includes one number at no charge, once per + account: it must be a US or Canadian number (a +1 number) costing $20 a month or + less. `isFreeEligible` in `GET /v1/phone-numbers/available` marks the numbers + that qualify. Claiming it spends the benefit for good, across every team the + account owner owns, so releasing that number does not make another one free. + + **Numbers with regulatory requirements.** Which numbers need regulatory + information is decided per number, not by a fixed country list. The purchase + looks the requirements up for the exact number before charging anything: + + 1. `GET /v1/phone-numbers/requirements?phoneNumber=...`. If `items` is empty, + buy normally. + 2. Create what it asks for: addresses with `POST /v1/addresses`, documents with + `POST /v1/documents`. + 3. Purchase with `type` and `regulatoryRequirements`. The number is bought and + billed at once with `regulatoryStatus: pending_review`. + 4. Poll `GET /v1/phone-numbers/{phoneNumberId}` until `regulatoryStatus` is + `approved`. Assign it to a sender before or after approval; it starts + carrying messages once approved. + + **Reuse.** Information you submitted is kept for your project, per country and + `type`, and a later purchase there may omit `regulatoryRequirements`. Reuse only + happens when what is kept still covers every requirement of the new number and + every address and document in it belongs to the project. Otherwise, or when + nothing is kept, the purchase returns `400 regulatory_compliance_required` with + the missing requirements in `details`. + + Invalid values (a missing, unknown or repeated requirement id, an address or + document from another project, or one rejected in review) return + `400 invalid_request`. If an address or document cannot be registered for + review, the purchase returns `400 invalid_request` naming the requirement. If + the requirements cannot be looked up, the purchase returns + `502 requirements_unavailable`, except for US and Canadian numbers, which are + sold as numbers without requirements. None of these errors charge anything. Args: phone_number: Phone number in E.164 format. name: Optional custom name for the phone number. + regulatory_requirements: Regulatory information, for numbers whose requirements list is not empty. Get + the list with `GET /v1/phone-numbers/requirements?phoneNumber=...` and send one + entry per requirement id, except `action` requirements, which take no value. + Every required id must be present, once, and no unknown id may be sent; + otherwise the purchase is refused with `400 invalid_request` before anything is + charged. + + The information is kept for your project under the number's country and `type`. + A later purchase there may omit this field if what is kept still covers that + number's requirements. Omit it for numbers without requirements. + + type: Type of phone number. `mobile` is stocked in countries where no geographic + (`local`) or non-geographic (`national`) inventory exists, and in several + markets it is the only type that can receive SMS. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -227,6 +277,8 @@ def purchase( { "phone_number": phone_number, "name": name, + "regulatory_requirements": regulatory_requirements, + "type": type, }, phone_number_purchase_params.PhoneNumberPurchaseParams, ), @@ -274,7 +326,8 @@ def release( def requirements( self, *, - country_code: str, + country_code: str | Omit = omit, + phone_number: str | Omit = omit, type: PhoneNumberType | 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. @@ -284,14 +337,31 @@ def requirements( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> PhoneNumberRequirementsResponse: """ - Get regulatory requirements for purchasing phone numbers in a specific country. - Some countries require additional documentation (addresses, identity documents) - before phone numbers can be activated. + Get the regulatory information needed to buy a phone number, for one specific + number or for a country and number type. Prefer `phoneNumber`: the response is + then exactly the list the purchase of that number validates against. Pass each + `requirementTypes[].id` back as `requirementType` in `regulatoryRequirements` on + `POST /v1/phone-numbers`. + + For `phoneNumber`, the requirements of that exact number are returned. When they + cannot be resolved for the number itself, the list for its country and `type` is + returned instead, and the purchase uses the same list. An empty `items` array + means the number needs no regulatory information. If the requirements cannot be + retrieved at all, the response is `502 requirements_unavailable`, never an empty + list. + + URL-encode the `+` of `phoneNumber` as `%2B`. An unencoded `+` is also accepted. Args: - country_code: Two-letter ISO country code. + country_code: Two-letter ISO country code. Required unless `phoneNumber` is given. + + phone_number: E.164 number from `GET /v1/phone-numbers/available`, with `+` encoded as `%2B`. + Returns the requirements the purchase of that number checks. Takes precedence + over `countryCode`. - type: Type of phone number (local, mobile, tollFree). + type: Type of phone number (local, national, mobile, tollFree). Defaults to `local`. + With `phoneNumber`, used only when the number's own requirements cannot be + resolved and the country list is returned. extra_headers: Send extra headers @@ -311,6 +381,7 @@ def requirements( query=maybe_transform( { "country_code": country_code, + "phone_number": phone_number, "type": type, }, phone_number_requirements_params.PhoneNumberRequirementsParams, @@ -451,7 +522,9 @@ async def update( Args: name: Custom name for the phone number. Set to null to clear. - sender_id: Sender ID to assign the phone number to. Set to null to unassign. + sender_id: Sender ID to assign the phone number to. Set to null to unassign. A number under + regulatory review is recorded now and connected to the sender when approved; a + rejected number is refused. extra_headers: Send extra headers @@ -532,6 +605,8 @@ async def purchase( *, phone_number: str, name: str | Omit = omit, + regulatory_requirements: Iterable[phone_number_purchase_params.RegulatoryRequirement] | Omit = omit, + type: PhoneNumberType | 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, @@ -542,17 +617,63 @@ async def purchase( """Purchase an available phone number. Requires a paid plan: the Free plan cannot - purchase phone numbers and receives `402` with code `paid_plan_required`. Paid - plans include one US number at no charge. The included number is one per account - and is granted once: claiming it spends the benefit for good, so releasing that - number does not make another one free, and numbers the account already bought do - not consume it. + purchase phone numbers and receives `402` with code `paid_plan_required`. + + **The included number.** A paid plan includes one number at no charge, once per + account: it must be a US or Canadian number (a +1 number) costing $20 a month or + less. `isFreeEligible` in `GET /v1/phone-numbers/available` marks the numbers + that qualify. Claiming it spends the benefit for good, across every team the + account owner owns, so releasing that number does not make another one free. + + **Numbers with regulatory requirements.** Which numbers need regulatory + information is decided per number, not by a fixed country list. The purchase + looks the requirements up for the exact number before charging anything: + + 1. `GET /v1/phone-numbers/requirements?phoneNumber=...`. If `items` is empty, + buy normally. + 2. Create what it asks for: addresses with `POST /v1/addresses`, documents with + `POST /v1/documents`. + 3. Purchase with `type` and `regulatoryRequirements`. The number is bought and + billed at once with `regulatoryStatus: pending_review`. + 4. Poll `GET /v1/phone-numbers/{phoneNumberId}` until `regulatoryStatus` is + `approved`. Assign it to a sender before or after approval; it starts + carrying messages once approved. + + **Reuse.** Information you submitted is kept for your project, per country and + `type`, and a later purchase there may omit `regulatoryRequirements`. Reuse only + happens when what is kept still covers every requirement of the new number and + every address and document in it belongs to the project. Otherwise, or when + nothing is kept, the purchase returns `400 regulatory_compliance_required` with + the missing requirements in `details`. + + Invalid values (a missing, unknown or repeated requirement id, an address or + document from another project, or one rejected in review) return + `400 invalid_request`. If an address or document cannot be registered for + review, the purchase returns `400 invalid_request` naming the requirement. If + the requirements cannot be looked up, the purchase returns + `502 requirements_unavailable`, except for US and Canadian numbers, which are + sold as numbers without requirements. None of these errors charge anything. Args: phone_number: Phone number in E.164 format. name: Optional custom name for the phone number. + regulatory_requirements: Regulatory information, for numbers whose requirements list is not empty. Get + the list with `GET /v1/phone-numbers/requirements?phoneNumber=...` and send one + entry per requirement id, except `action` requirements, which take no value. + Every required id must be present, once, and no unknown id may be sent; + otherwise the purchase is refused with `400 invalid_request` before anything is + charged. + + The information is kept for your project under the number's country and `type`. + A later purchase there may omit this field if what is kept still covers that + number's requirements. Omit it for numbers without requirements. + + type: Type of phone number. `mobile` is stocked in countries where no geographic + (`local`) or non-geographic (`national`) inventory exists, and in several + markets it is the only type that can receive SMS. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -567,6 +688,8 @@ async def purchase( { "phone_number": phone_number, "name": name, + "regulatory_requirements": regulatory_requirements, + "type": type, }, phone_number_purchase_params.PhoneNumberPurchaseParams, ), @@ -614,7 +737,8 @@ async def release( async def requirements( self, *, - country_code: str, + country_code: str | Omit = omit, + phone_number: str | Omit = omit, type: PhoneNumberType | 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. @@ -624,14 +748,31 @@ async def requirements( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> PhoneNumberRequirementsResponse: """ - Get regulatory requirements for purchasing phone numbers in a specific country. - Some countries require additional documentation (addresses, identity documents) - before phone numbers can be activated. + Get the regulatory information needed to buy a phone number, for one specific + number or for a country and number type. Prefer `phoneNumber`: the response is + then exactly the list the purchase of that number validates against. Pass each + `requirementTypes[].id` back as `requirementType` in `regulatoryRequirements` on + `POST /v1/phone-numbers`. + + For `phoneNumber`, the requirements of that exact number are returned. When they + cannot be resolved for the number itself, the list for its country and `type` is + returned instead, and the purchase uses the same list. An empty `items` array + means the number needs no regulatory information. If the requirements cannot be + retrieved at all, the response is `502 requirements_unavailable`, never an empty + list. + + URL-encode the `+` of `phoneNumber` as `%2B`. An unencoded `+` is also accepted. Args: - country_code: Two-letter ISO country code. + country_code: Two-letter ISO country code. Required unless `phoneNumber` is given. + + phone_number: E.164 number from `GET /v1/phone-numbers/available`, with `+` encoded as `%2B`. + Returns the requirements the purchase of that number checks. Takes precedence + over `countryCode`. - type: Type of phone number (local, mobile, tollFree). + type: Type of phone number (local, national, mobile, tollFree). Defaults to `local`. + With `phoneNumber`, used only when the number's own requirements cannot be + resolved and the country list is returned. extra_headers: Send extra headers @@ -651,6 +792,7 @@ async def requirements( query=await async_maybe_transform( { "country_code": country_code, + "phone_number": phone_number, "type": type, }, phone_number_requirements_params.PhoneNumberRequirementsParams, diff --git a/src/zavudev/resources/senders/senders.py b/src/zavudev/resources/senders/senders.py index ab19db8..ccf60a1 100644 --- a/src/zavudev/resources/senders/senders.py +++ b/src/zavudev/resources/senders/senders.py @@ -130,8 +130,10 @@ def create( email_from_name: Display name shown in the recipient's inbox for the email channel. - email_receiving_enabled: Enable inbound email receiving on this sender. Requires a verified MX record on - the domain; ignored otherwise. + email_receiving_enabled: Enable inbound email receiving on this sender. Requires a verified inbound MX + record on the domain; the request is ignored otherwise. Read + `emailReceivingEnabled` back off the response to see whether it was applied — it + comes back `false` when the MX has not verified. enable_sms_oneway: Enable the one-way SMS channel (`sms_oneway`). Needs nothing else — no phone number, no credential — so it is the fastest way to get a sender that can send. @@ -145,8 +147,10 @@ def create( phone_number: Phone number in E.164 format, and it must be a number your project already owns (see `GET /v1/phone-numbers`). The number is routed to the sender as part of this call, which is what turns the SMS channel on. Passing a number the project - does not own, or one already attached to another sender, returns 400 rather than - creating a sender that cannot send. Omit for an email-only sender. + does not own, one already attached to another sender, or one rejected in + regulatory review returns 400 rather than creating a sender that cannot send. A + number still under review is attached and starts carrying messages when it is + approved. Omit for an email-only sender. webhook_events: Events to subscribe to. @@ -271,7 +275,10 @@ def update( email_from_name: Display name shown in the recipient's inbox for the email channel. - email_receiving_enabled: Enable or disable inbound email receiving for this sender. + email_receiving_enabled: Enable or disable inbound email receiving for this sender. Enabling requires a + verified inbound MX record on the domain; the request is ignored otherwise, and + `emailReceivingEnabled` comes back `false` on the response. Disabling always + applies. enable_sms_oneway: Turn the one-way SMS channel on or off. Enabling needs nothing else and takes effect immediately; disabling removes the channel from the sender. Confirm with @@ -664,8 +671,10 @@ async def create( email_from_name: Display name shown in the recipient's inbox for the email channel. - email_receiving_enabled: Enable inbound email receiving on this sender. Requires a verified MX record on - the domain; ignored otherwise. + email_receiving_enabled: Enable inbound email receiving on this sender. Requires a verified inbound MX + record on the domain; the request is ignored otherwise. Read + `emailReceivingEnabled` back off the response to see whether it was applied — it + comes back `false` when the MX has not verified. enable_sms_oneway: Enable the one-way SMS channel (`sms_oneway`). Needs nothing else — no phone number, no credential — so it is the fastest way to get a sender that can send. @@ -679,8 +688,10 @@ async def create( phone_number: Phone number in E.164 format, and it must be a number your project already owns (see `GET /v1/phone-numbers`). The number is routed to the sender as part of this call, which is what turns the SMS channel on. Passing a number the project - does not own, or one already attached to another sender, returns 400 rather than - creating a sender that cannot send. Omit for an email-only sender. + does not own, one already attached to another sender, or one rejected in + regulatory review returns 400 rather than creating a sender that cannot send. A + number still under review is attached and starts carrying messages when it is + approved. Omit for an email-only sender. webhook_events: Events to subscribe to. @@ -805,7 +816,10 @@ async def update( email_from_name: Display name shown in the recipient's inbox for the email channel. - email_receiving_enabled: Enable or disable inbound email receiving for this sender. + email_receiving_enabled: Enable or disable inbound email receiving for this sender. Enabling requires a + verified inbound MX record on the domain; the request is ignored otherwise, and + `emailReceivingEnabled` comes back `false` on the response. Disabling always + applies. enable_sms_oneway: Turn the one-way SMS channel on or off. Enabling needs nothing else and takes effect immediately; disabling removes the channel from the sender. Confirm with diff --git a/src/zavudev/resources/templates.py b/src/zavudev/resources/templates.py index bb802f7..1334dd4 100644 --- a/src/zavudev/resources/templates.py +++ b/src/zavudev/resources/templates.py @@ -312,11 +312,18 @@ def sync( ) -> TemplateSyncResponse: """Reconcile this project's templates against WhatsApp. - Two things happen per + Three things happen per connected WhatsApp Business Account: templates that exist on Meta but not in - Zavu are imported (or linked to an existing template with the same name), and - the approval status of the templates Zavu already knows about is refreshed from - Meta. + Zavu are imported (or linked to an existing template with the same name), the + approval status of the templates Zavu already knows about is refreshed from + Meta, and their **category** is refreshed from Meta. + + The category matters because it is what each message is billed under, and Meta + reassigns it on its own — commonly `UTILITY` to `MARKETING`, on a template that + is already approved and whose status therefore never moves. A template whose + category changed but whose status did not is still counted in `updated`. This is + the way to repair templates whose category drifted before you started listening + for `template.status_changed`. This is what to call when a template was created outside Zavu — in Meta Business Manager, or by another tool — or when a `template.status_changed` webhook was @@ -635,11 +642,18 @@ async def sync( ) -> TemplateSyncResponse: """Reconcile this project's templates against WhatsApp. - Two things happen per + Three things happen per connected WhatsApp Business Account: templates that exist on Meta but not in - Zavu are imported (or linked to an existing template with the same name), and - the approval status of the templates Zavu already knows about is refreshed from - Meta. + Zavu are imported (or linked to an existing template with the same name), the + approval status of the templates Zavu already knows about is refreshed from + Meta, and their **category** is refreshed from Meta. + + The category matters because it is what each message is billed under, and Meta + reassigns it on its own — commonly `UTILITY` to `MARKETING`, on a template that + is already approved and whose status therefore never moves. A template whose + category changed but whose status did not is still counted in `updated`. This is + the way to repair templates whose category drifted before you started listening + for `template.status_changed`. This is what to call when a template was created outside Zavu — in Meta Business Manager, or by another tool — or when a `template.status_changed` webhook was diff --git a/src/zavudev/types/address_create_params.py b/src/zavudev/types/address_create_params.py index a014cd5..7c20576 100644 --- a/src/zavudev/types/address_create_params.py +++ b/src/zavudev/types/address_create_params.py @@ -12,6 +12,12 @@ class AddressCreateParams(TypedDict, total=False): country_code: Required[Annotated[str, PropertyInfo(alias="countryCode")]] + first_name: Required[Annotated[str, PropertyInfo(alias="firstName")]] + """First name of the person the address is registered to.""" + + last_name: Required[Annotated[str, PropertyInfo(alias="lastName")]] + """Last name of the person the address is registered to.""" + locality: Required[str] postal_code: Required[Annotated[str, PropertyInfo(alias="postalCode")]] @@ -21,9 +27,9 @@ class AddressCreateParams(TypedDict, total=False): administrative_area: Annotated[str, PropertyInfo(alias="administrativeArea")] business_name: Annotated[str, PropertyInfo(alias="businessName")] + """Business name, when the address belongs to a business. - extended_address: Annotated[str, PropertyInfo(alias="extendedAddress")] + Defaults to the person's full name. + """ - first_name: Annotated[str, PropertyInfo(alias="firstName")] - - last_name: Annotated[str, PropertyInfo(alias="lastName")] + extended_address: Annotated[str, PropertyInfo(alias="extendedAddress")] diff --git a/src/zavudev/types/number_10dlc/ten_dlc_brand.py b/src/zavudev/types/number_10dlc/ten_dlc_brand.py index a710477..f195ad8 100644 --- a/src/zavudev/types/number_10dlc/ten_dlc_brand.py +++ b/src/zavudev/types/number_10dlc/ten_dlc_brand.py @@ -38,8 +38,20 @@ class TenDlcBrand(BaseModel): state: str - status: Literal["draft", "pending", "verified", "rejected"] - """Status of a 10DLC brand registration.""" + status: Literal["draft", "pending", "verified", "unverified", "rejected", "failed"] + """Status of a 10DLC brand registration. + + - `draft`: created, not yet submitted to the carrier. + - `pending`: submitted, awaiting the carrier's answer. + - `verified`: the carrier registered the brand AND verified the business behind + it. + - `unverified`: the carrier registered the brand but did not verify the business + — the registration exists, the identity check did not pass or has not been + resolved. Campaigns are allowed, with lower daily limits. Read + `identityStatus` for the carrier's own wording. + - `rejected`: refused by the carrier. + - `failed`: the registration never reached the carrier; the fee is refunded. + """ street: str @@ -64,6 +76,14 @@ class TenDlcBrand(BaseModel): first_name: Optional[str] = FieldInfo(alias="firstName", default=None) + identity_status: Optional[str] = FieldInfo(alias="identityStatus", default=None) + """ + The carrier's raw identity verdict on the business, as the carrier spells it + (`VERIFIED`, `VETTED_VERIFIED`, `SELF_DECLARED`, `UNVERIFIED`). Null while the + identity has not been resolved — which is not the same as verified, and is why + such a brand reports `status: unverified`. + """ + last_name: Optional[str] = FieldInfo(alias="lastName", default=None) stock_exchange: Optional[str] = FieldInfo(alias="stockExchange", default=None) diff --git a/src/zavudev/types/owned_phone_number.py b/src/zavudev/types/owned_phone_number.py index e13925b..6550d5c 100644 --- a/src/zavudev/types/owned_phone_number.py +++ b/src/zavudev/types/owned_phone_number.py @@ -2,6 +2,7 @@ from typing import List, Optional from datetime import datetime +from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -23,7 +24,31 @@ class OwnedPhoneNumber(BaseModel): pricing: OwnedPhoneNumberPricing + regulatory_status: Literal["approved", "pending_review", "rejected"] = FieldInfo(alias="regulatoryStatus") + """Regulatory review state. + + Numbers that need no review are `approved` immediately. A number bought with + regulatory information is owned and billed from purchase and starts + `pending_review`; it cannot send messages or place calls until this is + `approved`. The state is re-checked every 6 hours: poll + `GET /v1/phone-numbers/{phoneNumberId}` to follow it. + + Assign it to a sender with `PATCH /v1/phone-numbers/{phoneNumberId}` + (`senderId`) before or after approval. A number assigned while under review is + recorded and connected to that sender when it is approved; the connection is + retried until it succeeds. A sender created over the API is set up for SMS as + part of the assignment. `rejected` means review refused the information: the + number cannot be assigned to a sender. A number that stays `pending_review` may + be waiting on information the API cannot supply; contact support. + """ + status: PhoneNumberStatus + """Billing state of an owned number, separate from `regulatoryStatus`. + + `pending` is legacy and is not written to numbers today. The SDKs carry + `active`, `suspended` and `pending` only; `releasing` and `released` are + returned by the REST API until their next release. + """ name: Optional[str] = None """Optional custom name for the phone number.""" diff --git a/src/zavudev/types/phone_number_pricing.py b/src/zavudev/types/phone_number_pricing.py index 0dc1b8a..6db023b 100644 --- a/src/zavudev/types/phone_number_pricing.py +++ b/src/zavudev/types/phone_number_pricing.py @@ -11,10 +11,11 @@ class PhoneNumberPricing(BaseModel): is_free_eligible: Optional[bool] = FieldInfo(alias="isFreeEligible", default=None) - """Whether this number qualifies as the plan-included US number on paid plans. - - The benefit is one per account: it is never offered again once claimed, not even - after the number is released. + """ + Whether this number qualifies as the plan-included number: a US or Canadian + number (a +1 number) costing $20 a month or less. The benefit is one per + account: it is never offered again once claimed, not even after the number is + released. """ monthly_price: Optional[float] = FieldInfo(alias="monthlyPrice", default=None) diff --git a/src/zavudev/types/phone_number_purchase_params.py b/src/zavudev/types/phone_number_purchase_params.py index 5928239..7938ea9 100644 --- a/src/zavudev/types/phone_number_purchase_params.py +++ b/src/zavudev/types/phone_number_purchase_params.py @@ -2,11 +2,13 @@ from __future__ import annotations +from typing import Iterable from typing_extensions import Required, Annotated, TypedDict from .._utils import PropertyInfo +from .phone_number_type import PhoneNumberType -__all__ = ["PhoneNumberPurchaseParams"] +__all__ = ["PhoneNumberPurchaseParams", "RegulatoryRequirement"] class PhoneNumberPurchaseParams(TypedDict, total=False): @@ -15,3 +17,42 @@ class PhoneNumberPurchaseParams(TypedDict, total=False): name: str """Optional custom name for the phone number.""" + + regulatory_requirements: Annotated[Iterable[RegulatoryRequirement], PropertyInfo(alias="regulatoryRequirements")] + """Regulatory information, for numbers whose requirements list is not empty. + + Get the list with `GET /v1/phone-numbers/requirements?phoneNumber=...` and send + one entry per requirement id, except `action` requirements, which take no value. + Every required id must be present, once, and no unknown id may be sent; + otherwise the purchase is refused with `400 invalid_request` before anything is + charged. + + The information is kept for your project under the number's country and `type`. + A later purchase there may omit this field if what is kept still covers that + number's requirements. Omit it for numbers without requirements. + """ + + type: PhoneNumberType + """Type of phone number. + + `mobile` is stocked in countries where no geographic (`local`) or non-geographic + (`national`) inventory exists, and in several markets it is the only type that + can receive SMS. + """ + + +class RegulatoryRequirement(TypedDict, total=False): + field_value: Required[Annotated[str, PropertyInfo(alias="fieldValue")]] + """ + Depends on the requirement's `type`: the text itself for `textual`; for + `address`, the `id` of an address created in this project with + `POST /v1/addresses`; for `document`, the `id` of a document created with + `POST /v1/documents`. An address or document from another project, or one + rejected in review, is refused. + """ + + requirement_type: Required[Annotated[str, PropertyInfo(alias="requirementType")]] + """A `requirementTypes[].id` from `GET /v1/phone-numbers/requirements`. + + Each id may appear only once. + """ diff --git a/src/zavudev/types/phone_number_requirements_params.py b/src/zavudev/types/phone_number_requirements_params.py index 964fa2d..c960244 100644 --- a/src/zavudev/types/phone_number_requirements_params.py +++ b/src/zavudev/types/phone_number_requirements_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import Required, Annotated, TypedDict +from typing_extensions import Annotated, TypedDict from .._utils import PropertyInfo from .phone_number_type import PhoneNumberType @@ -11,8 +11,19 @@ class PhoneNumberRequirementsParams(TypedDict, total=False): - country_code: Required[Annotated[str, PropertyInfo(alias="countryCode")]] - """Two-letter ISO country code.""" + country_code: Annotated[str, PropertyInfo(alias="countryCode")] + """Two-letter ISO country code. Required unless `phoneNumber` is given.""" + + phone_number: Annotated[str, PropertyInfo(alias="phoneNumber")] + """E.164 number from `GET /v1/phone-numbers/available`, with `+` encoded as `%2B`. + + Returns the requirements the purchase of that number checks. Takes precedence + over `countryCode`. + """ type: PhoneNumberType - """Type of phone number (local, mobile, tollFree).""" + """Type of phone number (local, national, mobile, tollFree). + + Defaults to `local`. With `phoneNumber`, used only when the number's own + requirements cannot be resolved and the country list is returned. + """ diff --git a/src/zavudev/types/phone_number_status.py b/src/zavudev/types/phone_number_status.py index 6dba633..a4b93a5 100644 --- a/src/zavudev/types/phone_number_status.py +++ b/src/zavudev/types/phone_number_status.py @@ -4,4 +4,4 @@ __all__ = ["PhoneNumberStatus"] -PhoneNumberStatus: TypeAlias = Literal["active", "suspended", "pending"] +PhoneNumberStatus: TypeAlias = Literal["active", "suspended", "pending", "releasing", "released"] diff --git a/src/zavudev/types/phone_number_update_params.py b/src/zavudev/types/phone_number_update_params.py index ba62aef..2a80e3d 100644 --- a/src/zavudev/types/phone_number_update_params.py +++ b/src/zavudev/types/phone_number_update_params.py @@ -15,4 +15,8 @@ class PhoneNumberUpdateParams(TypedDict, total=False): """Custom name for the phone number. Set to null to clear.""" sender_id: Annotated[Optional[str], PropertyInfo(alias="senderId")] - """Sender ID to assign the phone number to. Set to null to unassign.""" + """Sender ID to assign the phone number to. + + Set to null to unassign. A number under regulatory review is recorded now and + connected to the sender when approved; a rejected number is refused. + """ diff --git a/src/zavudev/types/requirement.py b/src/zavudev/types/requirement.py index 9ff01d5..dd39400 100644 --- a/src/zavudev/types/requirement.py +++ b/src/zavudev/types/requirement.py @@ -11,7 +11,9 @@ class Requirement(BaseModel): - """A group of requirements for a specific country/phone type combination.""" + """ + The requirements for ordering a number: for a country and number type, or for one specific number when requested with `phoneNumber` (then `id` is that phone number and `countryCode` is taken from it). + """ id: str diff --git a/src/zavudev/types/requirement_type.py b/src/zavudev/types/requirement_type.py index f656fb5..a6c3a71 100644 --- a/src/zavudev/types/requirement_type.py +++ b/src/zavudev/types/requirement_type.py @@ -15,6 +15,7 @@ class RequirementType(BaseModel): """A specific requirement type within a requirement group.""" id: str + """Send this as `requirementType` in `regulatoryRequirements` when purchasing.""" description: str diff --git a/src/zavudev/types/sender.py b/src/zavudev/types/sender.py index 4991ea6..eaf047a 100644 --- a/src/zavudev/types/sender.py +++ b/src/zavudev/types/sender.py @@ -49,11 +49,13 @@ class Sender(BaseModel): """Phone number in E.164 format.""" channels: Optional[List[str]] = None - """ - Channels this sender can actually send on right now, computed from its - configuration. Empty means the sender cannot send or receive anything yet: a - phoneNumber alone does not enable SMS or voice. Check this rather than inferring - capability from phoneNumber or emailAddress. + """Channels this sender can actually send on right now: configured AND activated. + + Empty means the sender cannot send or receive anything yet: a phoneNumber alone + does not enable SMS or voice, and a connected account that is not activated is + left out, because every send on it is refused. Check this rather than inferring + capability from phoneNumber or emailAddress, and turn a connected channel on + with `POST /v1/senders/{senderId}/channels/{channel}/activate`. """ created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) diff --git a/src/zavudev/types/sender_create_params.py b/src/zavudev/types/sender_create_params.py index 8bfb662..c6c4760 100644 --- a/src/zavudev/types/sender_create_params.py +++ b/src/zavudev/types/sender_create_params.py @@ -33,7 +33,9 @@ class SenderCreateParams(TypedDict, total=False): email_receiving_enabled: Annotated[bool, PropertyInfo(alias="emailReceivingEnabled")] """Enable inbound email receiving on this sender. - Requires a verified MX record on the domain; ignored otherwise. + Requires a verified inbound MX record on the domain; the request is ignored + otherwise. Read `emailReceivingEnabled` back off the response to see whether it + was applied — it comes back `false` when the MX has not verified. """ enable_sms_oneway: Annotated[bool, PropertyInfo(alias="enableSmsOneway")] @@ -56,8 +58,10 @@ class SenderCreateParams(TypedDict, total=False): Phone number in E.164 format, and it must be a number your project already owns (see `GET /v1/phone-numbers`). The number is routed to the sender as part of this call, which is what turns the SMS channel on. Passing a number the project - does not own, or one already attached to another sender, returns 400 rather than - creating a sender that cannot send. Omit for an email-only sender. + does not own, one already attached to another sender, or one rejected in + regulatory review returns 400 rather than creating a sender that cannot send. A + number still under review is attached and starts carrying messages when it is + approved. Omit for an email-only sender. """ set_as_default: Annotated[bool, PropertyInfo(alias="setAsDefault")] diff --git a/src/zavudev/types/sender_update_params.py b/src/zavudev/types/sender_update_params.py index 3e7e87d..8828d6c 100644 --- a/src/zavudev/types/sender_update_params.py +++ b/src/zavudev/types/sender_update_params.py @@ -37,7 +37,12 @@ class SenderUpdateParams(TypedDict, total=False): """Display name shown in the recipient's inbox for the email channel.""" email_receiving_enabled: Annotated[bool, PropertyInfo(alias="emailReceivingEnabled")] - """Enable or disable inbound email receiving for this sender.""" + """Enable or disable inbound email receiving for this sender. + + Enabling requires a verified inbound MX record on the domain; the request is + ignored otherwise, and `emailReceivingEnabled` comes back `false` on the + response. Disabling always applies. + """ enable_sms_oneway: Annotated[bool, PropertyInfo(alias="enableSmsOneway")] """Turn the one-way SMS channel on or off. diff --git a/src/zavudev/types/template_sync_response.py b/src/zavudev/types/template_sync_response.py index 8fa51e6..0b1098a 100644 --- a/src/zavudev/types/template_sync_response.py +++ b/src/zavudev/types/template_sync_response.py @@ -36,4 +36,7 @@ class TemplateSyncResponse(BaseModel): """ updated: int - """Templates whose approval status changed to match Meta.""" + """Templates brought back in line with Meta — approval status, category, or both. + + A template whose status and category both moved is counted once. + """ diff --git a/tests/api_resources/test_addresses.py b/tests/api_resources/test_addresses.py index e8e27e0..1d8b267 100644 --- a/tests/api_resources/test_addresses.py +++ b/tests/api_resources/test_addresses.py @@ -27,6 +27,8 @@ class TestAddresses: def test_method_create(self, client: Zavudev) -> None: address = client.addresses.create( country_code="DE", + first_name="John", + last_name="Doe", locality="Berlin", postal_code="10115", street_address="123 Main St", @@ -38,14 +40,14 @@ def test_method_create(self, client: Zavudev) -> None: def test_method_create_with_all_params(self, client: Zavudev) -> None: address = client.addresses.create( country_code="DE", + first_name="John", + last_name="Doe", locality="Berlin", postal_code="10115", street_address="123 Main St", administrative_area="administrativeArea", business_name="businessName", extended_address="extendedAddress", - first_name="John", - last_name="Doe", ) assert_matches_type(AddressCreateResponse, address, path=["response"]) @@ -54,6 +56,8 @@ def test_method_create_with_all_params(self, client: Zavudev) -> None: def test_raw_response_create(self, client: Zavudev) -> None: response = client.addresses.with_raw_response.create( country_code="DE", + first_name="John", + last_name="Doe", locality="Berlin", postal_code="10115", street_address="123 Main St", @@ -69,6 +73,8 @@ def test_raw_response_create(self, client: Zavudev) -> None: def test_streaming_response_create(self, client: Zavudev) -> None: with client.addresses.with_streaming_response.create( country_code="DE", + first_name="John", + last_name="Doe", locality="Berlin", postal_code="10115", street_address="123 Main St", @@ -213,6 +219,8 @@ class TestAsyncAddresses: async def test_method_create(self, async_client: AsyncZavudev) -> None: address = await async_client.addresses.create( country_code="DE", + first_name="John", + last_name="Doe", locality="Berlin", postal_code="10115", street_address="123 Main St", @@ -224,14 +232,14 @@ async def test_method_create(self, async_client: AsyncZavudev) -> None: async def test_method_create_with_all_params(self, async_client: AsyncZavudev) -> None: address = await async_client.addresses.create( country_code="DE", + first_name="John", + last_name="Doe", locality="Berlin", postal_code="10115", street_address="123 Main St", administrative_area="administrativeArea", business_name="businessName", extended_address="extendedAddress", - first_name="John", - last_name="Doe", ) assert_matches_type(AddressCreateResponse, address, path=["response"]) @@ -240,6 +248,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncZavudev) - async def test_raw_response_create(self, async_client: AsyncZavudev) -> None: response = await async_client.addresses.with_raw_response.create( country_code="DE", + first_name="John", + last_name="Doe", locality="Berlin", postal_code="10115", street_address="123 Main St", @@ -255,6 +265,8 @@ async def test_raw_response_create(self, async_client: AsyncZavudev) -> None: async def test_streaming_response_create(self, async_client: AsyncZavudev) -> None: async with async_client.addresses.with_streaming_response.create( country_code="DE", + first_name="John", + last_name="Doe", locality="Berlin", postal_code="10115", street_address="123 Main St", diff --git a/tests/api_resources/test_phone_numbers.py b/tests/api_resources/test_phone_numbers.py index 3e8f6ab..9c3a39c 100644 --- a/tests/api_resources/test_phone_numbers.py +++ b/tests/api_resources/test_phone_numbers.py @@ -171,6 +171,13 @@ def test_method_purchase_with_all_params(self, client: Zavudev) -> None: phone_number = client.phone_numbers.purchase( phone_number="+15551234567", name="Primary Line", + regulatory_requirements=[ + { + "field_value": "jd7x2k3m4n5p6q7r8s9t0abc", + "requirement_type": "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10", + } + ], + type="local", ) assert_matches_type(PhoneNumberPurchaseResponse, phone_number, path=["response"]) @@ -245,9 +252,7 @@ def test_path_params_release(self, client: Zavudev) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_requirements(self, client: Zavudev) -> None: - phone_number = client.phone_numbers.requirements( - country_code="xx", - ) + phone_number = client.phone_numbers.requirements() assert_matches_type(PhoneNumberRequirementsResponse, phone_number, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -255,6 +260,7 @@ def test_method_requirements(self, client: Zavudev) -> None: def test_method_requirements_with_all_params(self, client: Zavudev) -> None: phone_number = client.phone_numbers.requirements( country_code="xx", + phone_number="phoneNumber", type="local", ) assert_matches_type(PhoneNumberRequirementsResponse, phone_number, path=["response"]) @@ -262,9 +268,7 @@ def test_method_requirements_with_all_params(self, client: Zavudev) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_requirements(self, client: Zavudev) -> None: - response = client.phone_numbers.with_raw_response.requirements( - country_code="xx", - ) + response = client.phone_numbers.with_raw_response.requirements() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -274,9 +278,7 @@ def test_raw_response_requirements(self, client: Zavudev) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_requirements(self, client: Zavudev) -> None: - with client.phone_numbers.with_streaming_response.requirements( - country_code="xx", - ) as response: + with client.phone_numbers.with_streaming_response.requirements() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -483,6 +485,13 @@ async def test_method_purchase_with_all_params(self, async_client: AsyncZavudev) phone_number = await async_client.phone_numbers.purchase( phone_number="+15551234567", name="Primary Line", + regulatory_requirements=[ + { + "field_value": "jd7x2k3m4n5p6q7r8s9t0abc", + "requirement_type": "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10", + } + ], + type="local", ) assert_matches_type(PhoneNumberPurchaseResponse, phone_number, path=["response"]) @@ -557,9 +566,7 @@ async def test_path_params_release(self, async_client: AsyncZavudev) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_requirements(self, async_client: AsyncZavudev) -> None: - phone_number = await async_client.phone_numbers.requirements( - country_code="xx", - ) + phone_number = await async_client.phone_numbers.requirements() assert_matches_type(PhoneNumberRequirementsResponse, phone_number, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -567,6 +574,7 @@ async def test_method_requirements(self, async_client: AsyncZavudev) -> None: async def test_method_requirements_with_all_params(self, async_client: AsyncZavudev) -> None: phone_number = await async_client.phone_numbers.requirements( country_code="xx", + phone_number="phoneNumber", type="local", ) assert_matches_type(PhoneNumberRequirementsResponse, phone_number, path=["response"]) @@ -574,9 +582,7 @@ async def test_method_requirements_with_all_params(self, async_client: AsyncZavu @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_requirements(self, async_client: AsyncZavudev) -> None: - response = await async_client.phone_numbers.with_raw_response.requirements( - country_code="xx", - ) + response = await async_client.phone_numbers.with_raw_response.requirements() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -586,9 +592,7 @@ async def test_raw_response_requirements(self, async_client: AsyncZavudev) -> No @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_requirements(self, async_client: AsyncZavudev) -> None: - async with async_client.phone_numbers.with_streaming_response.requirements( - country_code="xx", - ) as response: + async with async_client.phone_numbers.with_streaming_response.requirements() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python"