From c1e89384e72a0a05e2a94819a2e8656872a804e2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 17:59:02 +0000 Subject: [PATCH 01/15] feat(api): api update --- .stats.yml | 4 +- .../Brands/BrandCreateResponseTest.cs | 7 ++++ .../Brands/BrandListPageResponseTest.cs | 11 +++++ .../Brands/BrandRetrieveResponseTest.cs | 7 ++++ .../Brands/BrandSubmitResponseTest.cs | 7 ++++ .../Brands/BrandSyncStatusResponseTest.cs | 7 ++++ .../Brands/BrandUpdateResponseTest.cs | 7 ++++ .../Number10dlc/Brands/TenDlcBrandTest.cs | 19 +++++++++ .../Models/Number10dlc/Brands/TenDlcBrand.cs | 41 +++++++++++++++++++ 9 files changed, 108 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index ddb1fe8..10360c4 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 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-997e957e7b8c6a15c356fd9182f73a1a149e78b702566daf23b1e7519c06ee95.yml +openapi_spec_hash: a0ee7a79588077d95d4bf9fccc3837a2 config_hash: 261e1b852ca7f8364e4a283a3edd350a diff --git a/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandCreateResponseTest.cs b/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandCreateResponseTest.cs index 43c2c6d..c1f5bb6 100644 --- a/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandCreateResponseTest.cs +++ b/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandCreateResponseTest.cs @@ -34,6 +34,7 @@ public void FieldRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -65,6 +66,7 @@ public void FieldRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -103,6 +105,7 @@ public void SerializationRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -148,6 +151,7 @@ public void FieldRoundtripThroughSerialization_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -186,6 +190,7 @@ public void FieldRoundtripThroughSerialization_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -224,6 +229,7 @@ public void Validation_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -263,6 +269,7 @@ public void CopyConstructor_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", diff --git a/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandListPageResponseTest.cs b/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandListPageResponseTest.cs index a156a87..7aeee6a 100644 --- a/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandListPageResponseTest.cs +++ b/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandListPageResponseTest.cs @@ -37,6 +37,7 @@ public void FieldRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -72,6 +73,7 @@ public void FieldRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -119,6 +121,7 @@ public void SerializationRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -168,6 +171,7 @@ public void FieldRoundtripThroughSerialization_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -210,6 +214,7 @@ public void FieldRoundtripThroughSerialization_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -257,6 +262,7 @@ public void Validation_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -300,6 +306,7 @@ public void OptionalNullablePropertiesUnsetAreNotSet_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -343,6 +350,7 @@ public void OptionalNullablePropertiesUnsetValidation_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -385,6 +393,7 @@ public void OptionalNullablePropertiesSetToNullAreSetToNull_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -430,6 +439,7 @@ public void OptionalNullablePropertiesSetToNullValidation_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -474,6 +484,7 @@ public void CopyConstructor_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", diff --git a/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandRetrieveResponseTest.cs b/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandRetrieveResponseTest.cs index d9f55f4..77a7e8e 100644 --- a/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandRetrieveResponseTest.cs +++ b/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandRetrieveResponseTest.cs @@ -34,6 +34,7 @@ public void FieldRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -65,6 +66,7 @@ public void FieldRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -103,6 +105,7 @@ public void SerializationRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -148,6 +151,7 @@ public void FieldRoundtripThroughSerialization_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -186,6 +190,7 @@ public void FieldRoundtripThroughSerialization_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -224,6 +229,7 @@ public void Validation_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -263,6 +269,7 @@ public void CopyConstructor_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", diff --git a/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandSubmitResponseTest.cs b/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandSubmitResponseTest.cs index 8be86d2..9bc2a8c 100644 --- a/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandSubmitResponseTest.cs +++ b/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandSubmitResponseTest.cs @@ -34,6 +34,7 @@ public void FieldRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -65,6 +66,7 @@ public void FieldRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -103,6 +105,7 @@ public void SerializationRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -148,6 +151,7 @@ public void FieldRoundtripThroughSerialization_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -186,6 +190,7 @@ public void FieldRoundtripThroughSerialization_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -224,6 +229,7 @@ public void Validation_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -263,6 +269,7 @@ public void CopyConstructor_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", diff --git a/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandSyncStatusResponseTest.cs b/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandSyncStatusResponseTest.cs index 0352ea2..85f6922 100644 --- a/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandSyncStatusResponseTest.cs +++ b/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandSyncStatusResponseTest.cs @@ -34,6 +34,7 @@ public void FieldRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -65,6 +66,7 @@ public void FieldRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -103,6 +105,7 @@ public void SerializationRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -148,6 +151,7 @@ public void FieldRoundtripThroughSerialization_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -186,6 +190,7 @@ public void FieldRoundtripThroughSerialization_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -224,6 +229,7 @@ public void Validation_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -263,6 +269,7 @@ public void CopyConstructor_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", diff --git a/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandUpdateResponseTest.cs b/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandUpdateResponseTest.cs index 5ecd2d8..c663036 100644 --- a/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandUpdateResponseTest.cs +++ b/src/Zavudev.Tests/Models/Number10dlc/Brands/BrandUpdateResponseTest.cs @@ -34,6 +34,7 @@ public void FieldRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -65,6 +66,7 @@ public void FieldRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -103,6 +105,7 @@ public void SerializationRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -148,6 +151,7 @@ public void FieldRoundtripThroughSerialization_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -186,6 +190,7 @@ public void FieldRoundtripThroughSerialization_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -224,6 +229,7 @@ public void Validation_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -263,6 +269,7 @@ public void CopyConstructor_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", diff --git a/src/Zavudev.Tests/Models/Number10dlc/Brands/TenDlcBrandTest.cs b/src/Zavudev.Tests/Models/Number10dlc/Brands/TenDlcBrandTest.cs index 85a89e7..819ec60 100644 --- a/src/Zavudev.Tests/Models/Number10dlc/Brands/TenDlcBrandTest.cs +++ b/src/Zavudev.Tests/Models/Number10dlc/Brands/TenDlcBrandTest.cs @@ -33,6 +33,7 @@ public void FieldRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -62,6 +63,7 @@ public void FieldRoundtrip_Works() string expectedEin = "12-3456789"; string expectedFailureReason = "failureReason"; string expectedFirstName = "firstName"; + string expectedIdentityStatus = "VERIFIED"; string expectedLastName = "lastName"; string expectedStockExchange = "stockExchange"; string expectedStockSymbol = "stockSymbol"; @@ -89,6 +91,7 @@ public void FieldRoundtrip_Works() Assert.Equal(expectedEin, model.Ein); Assert.Equal(expectedFailureReason, model.FailureReason); Assert.Equal(expectedFirstName, model.FirstName); + Assert.Equal(expectedIdentityStatus, model.IdentityStatus); Assert.Equal(expectedLastName, model.LastName); Assert.Equal(expectedStockExchange, model.StockExchange); Assert.Equal(expectedStockSymbol, model.StockSymbol); @@ -122,6 +125,7 @@ public void SerializationRoundtrip_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -164,6 +168,7 @@ public void FieldRoundtripThroughSerialization_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -200,6 +205,7 @@ public void FieldRoundtripThroughSerialization_Works() string expectedEin = "12-3456789"; string expectedFailureReason = "failureReason"; string expectedFirstName = "firstName"; + string expectedIdentityStatus = "VERIFIED"; string expectedLastName = "lastName"; string expectedStockExchange = "stockExchange"; string expectedStockSymbol = "stockSymbol"; @@ -227,6 +233,7 @@ public void FieldRoundtripThroughSerialization_Works() Assert.Equal(expectedEin, deserialized.Ein); Assert.Equal(expectedFailureReason, deserialized.FailureReason); Assert.Equal(expectedFirstName, deserialized.FirstName); + Assert.Equal(expectedIdentityStatus, deserialized.IdentityStatus); Assert.Equal(expectedLastName, deserialized.LastName); Assert.Equal(expectedStockExchange, deserialized.StockExchange); Assert.Equal(expectedStockSymbol, deserialized.StockSymbol); @@ -260,6 +267,7 @@ public void Validation_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -304,6 +312,8 @@ public void OptionalNullablePropertiesUnsetAreNotSet_Works() Assert.False(model.RawData.ContainsKey("failureReason")); Assert.Null(model.FirstName); Assert.False(model.RawData.ContainsKey("firstName")); + Assert.Null(model.IdentityStatus); + Assert.False(model.RawData.ContainsKey("identityStatus")); Assert.Null(model.LastName); Assert.False(model.RawData.ContainsKey("lastName")); Assert.Null(model.StockExchange); @@ -368,6 +378,7 @@ public void OptionalNullablePropertiesSetToNullAreSetToNull_Works() Ein = null, FailureReason = null, FirstName = null, + IdentityStatus = null, LastName = null, StockExchange = null, StockSymbol = null, @@ -388,6 +399,8 @@ public void OptionalNullablePropertiesSetToNullAreSetToNull_Works() Assert.True(model.RawData.ContainsKey("failureReason")); Assert.Null(model.FirstName); Assert.True(model.RawData.ContainsKey("firstName")); + Assert.Null(model.IdentityStatus); + Assert.True(model.RawData.ContainsKey("identityStatus")); Assert.Null(model.LastName); Assert.True(model.RawData.ContainsKey("lastName")); Assert.Null(model.StockExchange); @@ -428,6 +441,7 @@ public void OptionalNullablePropertiesSetToNullValidation_Works() Ein = null, FailureReason = null, FirstName = null, + IdentityStatus = null, LastName = null, StockExchange = null, StockSymbol = null, @@ -464,6 +478,7 @@ public void CopyConstructor_Works() Ein = "12-3456789", FailureReason = "failureReason", FirstName = "firstName", + IdentityStatus = "VERIFIED", LastName = "lastName", StockExchange = "stockExchange", StockSymbol = "stockSymbol", @@ -548,7 +563,9 @@ public class StatusTest : TestBase [InlineData(Status.Draft)] [InlineData(Status.Pending)] [InlineData(Status.Verified)] + [InlineData(Status.Unverified)] [InlineData(Status.Rejected)] + [InlineData(Status.Failed)] public void Validation_Works(Status rawValue) { // force implicit conversion because Theory can't do that for us @@ -572,7 +589,9 @@ public void InvalidEnumValidationThrows_Works() [InlineData(Status.Draft)] [InlineData(Status.Pending)] [InlineData(Status.Verified)] + [InlineData(Status.Unverified)] [InlineData(Status.Rejected)] + [InlineData(Status.Failed)] public void SerializationRoundtrip_Works(Status rawValue) { // force implicit conversion because Theory can't do that for us diff --git a/src/Zavudev/Models/Number10dlc/Brands/TenDlcBrand.cs b/src/Zavudev/Models/Number10dlc/Brands/TenDlcBrand.cs index c01be15..d596f2f 100644 --- a/src/Zavudev/Models/Number10dlc/Brands/TenDlcBrand.cs +++ b/src/Zavudev/Models/Number10dlc/Brands/TenDlcBrand.cs @@ -128,6 +128,15 @@ public required string State /// /// 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. /// public required ApiEnum Status { @@ -244,6 +253,22 @@ public string? FirstName init { this._rawData.Set("firstName", value); } } + /// + /// 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`. + /// + public string? IdentityStatus + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("identityStatus"); + } + init { this._rawData.Set("identityStatus", value); } + } + public string? LastName { get @@ -327,6 +352,7 @@ public override void Validate() _ = this.Ein; _ = this.FailureReason; _ = this.FirstName; + _ = this.IdentityStatus; _ = this.LastName; _ = this.StockExchange; _ = this.StockSymbol; @@ -428,6 +454,15 @@ JsonSerializerOptions options /// /// 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. /// [JsonConverter(typeof(StatusConverter))] public enum Status @@ -435,7 +470,9 @@ public enum Status Draft, Pending, Verified, + Unverified, Rejected, + Failed, } sealed class StatusConverter : JsonConverter @@ -451,7 +488,9 @@ JsonSerializerOptions options "draft" => Status.Draft, "pending" => Status.Pending, "verified" => Status.Verified, + "unverified" => Status.Unverified, "rejected" => Status.Rejected, + "failed" => Status.Failed, _ => (Status)(-1), }; } @@ -465,7 +504,9 @@ public override void Write(Utf8JsonWriter writer, Status value, JsonSerializerOp Status.Draft => "draft", Status.Pending => "pending", Status.Verified => "verified", + Status.Unverified => "unverified", Status.Rejected => "rejected", + Status.Failed => "failed", _ => throw new ZavudevInvalidDataException( string.Format("Invalid value '{0}' in {1}", value, nameof(value)) ), From 710ab9f1f328f1aeef304b058650140f0f0285db Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 08:21:37 +0000 Subject: [PATCH 02/15] feat(api): api update --- .stats.yml | 4 +- .../Addresses/AddressCreateParamsTest.cs | 32 ++-- .../PhoneNumbers/OwnedPhoneNumberTest.cs | 74 ++++++++ .../PhoneNumberListPageResponseTest.cs | 11 ++ .../PhoneNumberPurchaseParamsTest.cs | 133 +++++++++++++ .../PhoneNumberPurchaseResponseTest.cs | 7 + .../PhoneNumberRequirementsParamsTest.cs | 21 ++- .../PhoneNumberRetrieveResponseTest.cs | 7 + .../PhoneNumbers/PhoneNumberStatusTest.cs | 4 + .../PhoneNumberUpdateResponseTest.cs | 7 + .../Services/AddressServiceTest.cs | 2 + .../Services/PhoneNumberServiceTest.cs | 2 +- src/Zavudev/Core/ModelBase.cs | 1 + .../Models/Addresses/AddressCreateParams.cs | 70 ++++--- .../Models/Addresses/AddressDeleteParams.cs | 4 +- .../Models/PhoneNumbers/OwnedPhoneNumber.cs | 97 ++++++++++ .../Models/PhoneNumbers/PhoneNumberPricing.cs | 6 +- .../PhoneNumbers/PhoneNumberPurchaseParams.cs | 176 +++++++++++++++++- .../PhoneNumberRequirementsParams.cs | 60 +++++- .../Models/PhoneNumbers/PhoneNumberStatus.cs | 12 ++ .../PhoneNumbers/PhoneNumberUpdateParams.cs | 4 +- .../Models/PhoneNumbers/Requirement.cs | 4 +- .../Models/PhoneNumbers/RequirementType.cs | 3 + .../Models/Senders/SenderCreateParams.cs | 6 +- src/Zavudev/Services/IAddressService.cs | 9 +- src/Zavudev/Services/IPhoneNumberService.cs | 64 +++++-- src/Zavudev/Services/PhoneNumberService.cs | 6 +- 27 files changed, 726 insertions(+), 100 deletions(-) diff --git a/.stats.yml b/.stats.yml index 10360c4..bbcd963 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-997e957e7b8c6a15c356fd9182f73a1a149e78b702566daf23b1e7519c06ee95.yml -openapi_spec_hash: a0ee7a79588077d95d4bf9fccc3837a2 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-6a4c9cdb8d971155ac22e679a37d27525a97b8a65991908a1c12969252457811.yml +openapi_spec_hash: 5d999f34f096d592deaa6319f52190b3 config_hash: 261e1b852ca7f8364e4a283a3edd350a diff --git a/src/Zavudev.Tests/Models/Addresses/AddressCreateParamsTest.cs b/src/Zavudev.Tests/Models/Addresses/AddressCreateParamsTest.cs index a67d4b7..50d3c19 100644 --- a/src/Zavudev.Tests/Models/Addresses/AddressCreateParamsTest.cs +++ b/src/Zavudev.Tests/Models/Addresses/AddressCreateParamsTest.cs @@ -11,35 +11,35 @@ public void FieldRoundtrip_Works() var parameters = new AddressCreateParams { CountryCode = "DE", + FirstName = "John", + LastName = "Doe", Locality = "Berlin", PostalCode = "10115", StreetAddress = "123 Main St", AdministrativeArea = "administrativeArea", BusinessName = "businessName", ExtendedAddress = "extendedAddress", - FirstName = "John", - LastName = "Doe", }; string expectedCountryCode = "DE"; + string expectedFirstName = "John"; + string expectedLastName = "Doe"; string expectedLocality = "Berlin"; string expectedPostalCode = "10115"; string expectedStreetAddress = "123 Main St"; string expectedAdministrativeArea = "administrativeArea"; string expectedBusinessName = "businessName"; string expectedExtendedAddress = "extendedAddress"; - string expectedFirstName = "John"; - string expectedLastName = "Doe"; Assert.Equal(expectedCountryCode, parameters.CountryCode); + Assert.Equal(expectedFirstName, parameters.FirstName); + Assert.Equal(expectedLastName, parameters.LastName); Assert.Equal(expectedLocality, parameters.Locality); Assert.Equal(expectedPostalCode, parameters.PostalCode); Assert.Equal(expectedStreetAddress, parameters.StreetAddress); Assert.Equal(expectedAdministrativeArea, parameters.AdministrativeArea); Assert.Equal(expectedBusinessName, parameters.BusinessName); Assert.Equal(expectedExtendedAddress, parameters.ExtendedAddress); - Assert.Equal(expectedFirstName, parameters.FirstName); - Assert.Equal(expectedLastName, parameters.LastName); } [Fact] @@ -48,6 +48,8 @@ public void OptionalNonNullableParamsUnsetAreNotSet_Works() var parameters = new AddressCreateParams { CountryCode = "DE", + FirstName = "John", + LastName = "Doe", Locality = "Berlin", PostalCode = "10115", StreetAddress = "123 Main St", @@ -59,10 +61,6 @@ public void OptionalNonNullableParamsUnsetAreNotSet_Works() Assert.False(parameters.RawBodyData.ContainsKey("businessName")); Assert.Null(parameters.ExtendedAddress); Assert.False(parameters.RawBodyData.ContainsKey("extendedAddress")); - Assert.Null(parameters.FirstName); - Assert.False(parameters.RawBodyData.ContainsKey("firstName")); - Assert.Null(parameters.LastName); - Assert.False(parameters.RawBodyData.ContainsKey("lastName")); } [Fact] @@ -71,6 +69,8 @@ public void OptionalNonNullableParamsSetToNullAreNotSet_Works() var parameters = new AddressCreateParams { CountryCode = "DE", + FirstName = "John", + LastName = "Doe", Locality = "Berlin", PostalCode = "10115", StreetAddress = "123 Main St", @@ -79,8 +79,6 @@ public void OptionalNonNullableParamsSetToNullAreNotSet_Works() AdministrativeArea = null, BusinessName = null, ExtendedAddress = null, - FirstName = null, - LastName = null, }; Assert.Null(parameters.AdministrativeArea); @@ -89,10 +87,6 @@ public void OptionalNonNullableParamsSetToNullAreNotSet_Works() Assert.False(parameters.RawBodyData.ContainsKey("businessName")); Assert.Null(parameters.ExtendedAddress); Assert.False(parameters.RawBodyData.ContainsKey("extendedAddress")); - Assert.Null(parameters.FirstName); - Assert.False(parameters.RawBodyData.ContainsKey("firstName")); - Assert.Null(parameters.LastName); - Assert.False(parameters.RawBodyData.ContainsKey("lastName")); } [Fact] @@ -101,6 +95,8 @@ public void Url_Works() AddressCreateParams parameters = new() { CountryCode = "DE", + FirstName = "John", + LastName = "Doe", Locality = "Berlin", PostalCode = "10115", StreetAddress = "123 Main St", @@ -117,14 +113,14 @@ public void CopyConstructor_Works() var parameters = new AddressCreateParams { CountryCode = "DE", + FirstName = "John", + LastName = "Doe", Locality = "Berlin", PostalCode = "10115", StreetAddress = "123 Main St", AdministrativeArea = "administrativeArea", BusinessName = "businessName", ExtendedAddress = "extendedAddress", - FirstName = "John", - LastName = "Doe", }; AddressCreateParams copied = new(parameters); diff --git a/src/Zavudev.Tests/Models/PhoneNumbers/OwnedPhoneNumberTest.cs b/src/Zavudev.Tests/Models/PhoneNumbers/OwnedPhoneNumberTest.cs index 1ae8af4..161645a 100644 --- a/src/Zavudev.Tests/Models/PhoneNumbers/OwnedPhoneNumberTest.cs +++ b/src/Zavudev.Tests/Models/PhoneNumbers/OwnedPhoneNumberTest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text.Json; using Zavudev.Core; +using Zavudev.Exceptions; using Zavudev.Models.PhoneNumbers; namespace Zavudev.Tests.Models.PhoneNumbers; @@ -24,6 +25,7 @@ public void FieldRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -42,6 +44,7 @@ public void FieldRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }; + ApiEnum expectedRegulatoryStatus = RegulatoryStatus.Approved; ApiEnum expectedStatus = PhoneNumberStatus.Active; string expectedName = "name"; DateTimeOffset expectedNextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"); @@ -57,6 +60,7 @@ public void FieldRoundtrip_Works() Assert.Equal(expectedCreatedAt, model.CreatedAt); Assert.Equal(expectedPhoneNumber, model.PhoneNumber); Assert.Equal(expectedPricing, model.Pricing); + Assert.Equal(expectedRegulatoryStatus, model.RegulatoryStatus); Assert.Equal(expectedStatus, model.Status); Assert.Equal(expectedName, model.Name); Assert.Equal(expectedNextRenewalDate, model.NextRenewalDate); @@ -80,6 +84,7 @@ public void SerializationRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -112,6 +117,7 @@ public void FieldRoundtripThroughSerialization_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -137,6 +143,7 @@ public void FieldRoundtripThroughSerialization_Works() MonthlyPrice = 0, UpfrontCost = 0, }; + ApiEnum expectedRegulatoryStatus = RegulatoryStatus.Approved; ApiEnum expectedStatus = PhoneNumberStatus.Active; string expectedName = "name"; DateTimeOffset expectedNextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"); @@ -152,6 +159,7 @@ public void FieldRoundtripThroughSerialization_Works() Assert.Equal(expectedCreatedAt, deserialized.CreatedAt); Assert.Equal(expectedPhoneNumber, deserialized.PhoneNumber); Assert.Equal(expectedPricing, deserialized.Pricing); + Assert.Equal(expectedRegulatoryStatus, deserialized.RegulatoryStatus); Assert.Equal(expectedStatus, deserialized.Status); Assert.Equal(expectedName, deserialized.Name); Assert.Equal(expectedNextRenewalDate, deserialized.NextRenewalDate); @@ -175,6 +183,7 @@ public void Validation_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -201,6 +210,7 @@ public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, }; @@ -230,6 +240,7 @@ public void OptionalNonNullablePropertiesUnsetValidation_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, }; @@ -252,6 +263,7 @@ public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, // Null should be interpreted as omitted for these properties @@ -287,6 +299,7 @@ public void OptionalNonNullablePropertiesSetToNullValidation_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, // Null should be interpreted as omitted for these properties @@ -315,6 +328,7 @@ public void CopyConstructor_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -327,3 +341,63 @@ public void CopyConstructor_Works() Assert.Equal(model, copied); } } + +public class RegulatoryStatusTest : TestBase +{ + [Theory] + [InlineData(RegulatoryStatus.Approved)] + [InlineData(RegulatoryStatus.PendingReview)] + [InlineData(RegulatoryStatus.Rejected)] + public void Validation_Works(RegulatoryStatus rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + value.Validate(); + } + + [Fact] + public void InvalidEnumValidationThrows_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + + Assert.NotNull(value); + Assert.Throws(() => value.Validate()); + } + + [Theory] + [InlineData(RegulatoryStatus.Approved)] + [InlineData(RegulatoryStatus.PendingReview)] + [InlineData(RegulatoryStatus.Rejected)] + public void SerializationRoundtrip_Works(RegulatoryStatus rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize>( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(value, deserialized); + } + + [Fact] + public void InvalidEnumSerializationRoundtrip_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize>( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(value, deserialized); + } +} diff --git a/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberListPageResponseTest.cs b/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberListPageResponseTest.cs index 553206c..95c363c 100644 --- a/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberListPageResponseTest.cs +++ b/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberListPageResponseTest.cs @@ -28,6 +28,7 @@ public void FieldRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -53,6 +54,7 @@ public void FieldRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -90,6 +92,7 @@ public void SerializationRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -129,6 +132,7 @@ public void FieldRoundtripThroughSerialization_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -161,6 +165,7 @@ public void FieldRoundtripThroughSerialization_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -198,6 +203,7 @@ public void Validation_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -231,6 +237,7 @@ public void OptionalNullablePropertiesUnsetAreNotSet_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -264,6 +271,7 @@ public void OptionalNullablePropertiesUnsetValidation_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -296,6 +304,7 @@ public void OptionalNullablePropertiesSetToNullAreSetToNull_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -331,6 +340,7 @@ public void OptionalNullablePropertiesSetToNullValidation_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -365,6 +375,7 @@ public void CopyConstructor_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), diff --git a/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberPurchaseParamsTest.cs b/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberPurchaseParamsTest.cs index 467b80f..297d65d 100644 --- a/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberPurchaseParamsTest.cs +++ b/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberPurchaseParamsTest.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Text.Json; +using Zavudev.Core; using Zavudev.Models.PhoneNumbers; namespace Zavudev.Tests.Models.PhoneNumbers; @@ -12,13 +15,38 @@ public void FieldRoundtrip_Works() { PhoneNumber = "+15551234567", Name = "Primary Line", + RegulatoryRequirements = + [ + new() + { + FieldValue = "jd7x2k3m4n5p6q7r8s9t0abc", + RequirementType = "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10", + }, + ], + Type = PhoneNumberType.Local, }; string expectedPhoneNumber = "+15551234567"; string expectedName = "Primary Line"; + List expectedRegulatoryRequirements = + [ + new() + { + FieldValue = "jd7x2k3m4n5p6q7r8s9t0abc", + RequirementType = "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10", + }, + ]; + ApiEnum expectedType = PhoneNumberType.Local; Assert.Equal(expectedPhoneNumber, parameters.PhoneNumber); Assert.Equal(expectedName, parameters.Name); + Assert.NotNull(parameters.RegulatoryRequirements); + Assert.Equal(expectedRegulatoryRequirements.Count, parameters.RegulatoryRequirements.Count); + for (int i = 0; i < expectedRegulatoryRequirements.Count; i++) + { + Assert.Equal(expectedRegulatoryRequirements[i], parameters.RegulatoryRequirements[i]); + } + Assert.Equal(expectedType, parameters.Type); } [Fact] @@ -28,6 +56,10 @@ public void OptionalNonNullableParamsUnsetAreNotSet_Works() Assert.Null(parameters.Name); Assert.False(parameters.RawBodyData.ContainsKey("name")); + Assert.Null(parameters.RegulatoryRequirements); + Assert.False(parameters.RawBodyData.ContainsKey("regulatoryRequirements")); + Assert.Null(parameters.Type); + Assert.False(parameters.RawBodyData.ContainsKey("type")); } [Fact] @@ -39,10 +71,16 @@ public void OptionalNonNullableParamsSetToNullAreNotSet_Works() // Null should be interpreted as omitted for these properties Name = null, + RegulatoryRequirements = null, + Type = null, }; Assert.Null(parameters.Name); Assert.False(parameters.RawBodyData.ContainsKey("name")); + Assert.Null(parameters.RegulatoryRequirements); + Assert.False(parameters.RawBodyData.ContainsKey("regulatoryRequirements")); + Assert.Null(parameters.Type); + Assert.False(parameters.RawBodyData.ContainsKey("type")); } [Fact] @@ -62,6 +100,15 @@ public void CopyConstructor_Works() { PhoneNumber = "+15551234567", Name = "Primary Line", + RegulatoryRequirements = + [ + new() + { + FieldValue = "jd7x2k3m4n5p6q7r8s9t0abc", + RequirementType = "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10", + }, + ], + Type = PhoneNumberType.Local, }; PhoneNumberPurchaseParams copied = new(parameters); @@ -69,3 +116,89 @@ public void CopyConstructor_Works() Assert.Equal(parameters, copied); } } + +public class RegulatoryRequirementTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new RegulatoryRequirement + { + FieldValue = "jd7x2k3m4n5p6q7r8s9t0abc", + RequirementType = "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10", + }; + + string expectedFieldValue = "jd7x2k3m4n5p6q7r8s9t0abc"; + string expectedRequirementType = "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10"; + + Assert.Equal(expectedFieldValue, model.FieldValue); + Assert.Equal(expectedRequirementType, model.RequirementType); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new RegulatoryRequirement + { + FieldValue = "jd7x2k3m4n5p6q7r8s9t0abc", + RequirementType = "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10", + }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new RegulatoryRequirement + { + FieldValue = "jd7x2k3m4n5p6q7r8s9t0abc", + RequirementType = "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10", + }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + string expectedFieldValue = "jd7x2k3m4n5p6q7r8s9t0abc"; + string expectedRequirementType = "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10"; + + Assert.Equal(expectedFieldValue, deserialized.FieldValue); + Assert.Equal(expectedRequirementType, deserialized.RequirementType); + } + + [Fact] + public void Validation_Works() + { + var model = new RegulatoryRequirement + { + FieldValue = "jd7x2k3m4n5p6q7r8s9t0abc", + RequirementType = "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10", + }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new RegulatoryRequirement + { + FieldValue = "jd7x2k3m4n5p6q7r8s9t0abc", + RequirementType = "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10", + }; + + RegulatoryRequirement copied = new(model); + + Assert.Equal(model, copied); + } +} diff --git a/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberPurchaseResponseTest.cs b/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberPurchaseResponseTest.cs index 3b78216..f37a293 100644 --- a/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberPurchaseResponseTest.cs +++ b/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberPurchaseResponseTest.cs @@ -25,6 +25,7 @@ public void FieldRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -46,6 +47,7 @@ public void FieldRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -74,6 +76,7 @@ public void SerializationRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -109,6 +112,7 @@ public void FieldRoundtripThroughSerialization_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -137,6 +141,7 @@ public void FieldRoundtripThroughSerialization_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -165,6 +170,7 @@ public void Validation_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -194,6 +200,7 @@ public void CopyConstructor_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), diff --git a/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberRequirementsParamsTest.cs b/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberRequirementsParamsTest.cs index 1f4a908..922e0c5 100644 --- a/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberRequirementsParamsTest.cs +++ b/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberRequirementsParamsTest.cs @@ -12,21 +12,28 @@ public void FieldRoundtrip_Works() var parameters = new PhoneNumberRequirementsParams { CountryCode = "xx", + PhoneNumber = "phoneNumber", Type = PhoneNumberType.Local, }; string expectedCountryCode = "xx"; + string expectedPhoneNumber = "phoneNumber"; ApiEnum expectedType = PhoneNumberType.Local; Assert.Equal(expectedCountryCode, parameters.CountryCode); + Assert.Equal(expectedPhoneNumber, parameters.PhoneNumber); Assert.Equal(expectedType, parameters.Type); } [Fact] public void OptionalNonNullableParamsUnsetAreNotSet_Works() { - var parameters = new PhoneNumberRequirementsParams { CountryCode = "xx" }; + var parameters = new PhoneNumberRequirementsParams { }; + Assert.Null(parameters.CountryCode); + Assert.False(parameters.RawQueryData.ContainsKey("countryCode")); + Assert.Null(parameters.PhoneNumber); + Assert.False(parameters.RawQueryData.ContainsKey("phoneNumber")); Assert.Null(parameters.Type); Assert.False(parameters.RawQueryData.ContainsKey("type")); } @@ -36,12 +43,16 @@ public void OptionalNonNullableParamsSetToNullAreNotSet_Works() { var parameters = new PhoneNumberRequirementsParams { - CountryCode = "xx", - // Null should be interpreted as omitted for these properties + CountryCode = null, + PhoneNumber = null, Type = null, }; + Assert.Null(parameters.CountryCode); + Assert.False(parameters.RawQueryData.ContainsKey("countryCode")); + Assert.Null(parameters.PhoneNumber); + Assert.False(parameters.RawQueryData.ContainsKey("phoneNumber")); Assert.Null(parameters.Type); Assert.False(parameters.RawQueryData.ContainsKey("type")); } @@ -52,6 +63,7 @@ public void Url_Works() PhoneNumberRequirementsParams parameters = new() { CountryCode = "xx", + PhoneNumber = "phoneNumber", Type = PhoneNumberType.Local, }; @@ -60,7 +72,7 @@ public void Url_Works() Assert.True( TestBase.UrisEqual( new Uri( - "https://api.zavu.dev/v1/phone-numbers/requirements?countryCode=xx&type=local" + "https://api.zavu.dev/v1/phone-numbers/requirements?countryCode=xx&phoneNumber=phoneNumber&type=local" ), url ) @@ -73,6 +85,7 @@ public void CopyConstructor_Works() var parameters = new PhoneNumberRequirementsParams { CountryCode = "xx", + PhoneNumber = "phoneNumber", Type = PhoneNumberType.Local, }; diff --git a/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberRetrieveResponseTest.cs b/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberRetrieveResponseTest.cs index 3e564c9..09d4c92 100644 --- a/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberRetrieveResponseTest.cs +++ b/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberRetrieveResponseTest.cs @@ -25,6 +25,7 @@ public void FieldRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -46,6 +47,7 @@ public void FieldRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -74,6 +76,7 @@ public void SerializationRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -109,6 +112,7 @@ public void FieldRoundtripThroughSerialization_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -137,6 +141,7 @@ public void FieldRoundtripThroughSerialization_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -165,6 +170,7 @@ public void Validation_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -194,6 +200,7 @@ public void CopyConstructor_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), diff --git a/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberStatusTest.cs b/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberStatusTest.cs index 3b192ae..c9d5823 100644 --- a/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberStatusTest.cs +++ b/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberStatusTest.cs @@ -11,6 +11,8 @@ public class PhoneNumberStatusTest : TestBase [InlineData(PhoneNumberStatus.Active)] [InlineData(PhoneNumberStatus.Suspended)] [InlineData(PhoneNumberStatus.Pending)] + [InlineData(PhoneNumberStatus.Releasing)] + [InlineData(PhoneNumberStatus.Released)] public void Validation_Works(PhoneNumberStatus rawValue) { // force implicit conversion because Theory can't do that for us @@ -34,6 +36,8 @@ public void InvalidEnumValidationThrows_Works() [InlineData(PhoneNumberStatus.Active)] [InlineData(PhoneNumberStatus.Suspended)] [InlineData(PhoneNumberStatus.Pending)] + [InlineData(PhoneNumberStatus.Releasing)] + [InlineData(PhoneNumberStatus.Released)] public void SerializationRoundtrip_Works(PhoneNumberStatus rawValue) { // force implicit conversion because Theory can't do that for us diff --git a/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberUpdateResponseTest.cs b/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberUpdateResponseTest.cs index 3bcc4a5..803ea6d 100644 --- a/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberUpdateResponseTest.cs +++ b/src/Zavudev.Tests/Models/PhoneNumbers/PhoneNumberUpdateResponseTest.cs @@ -25,6 +25,7 @@ public void FieldRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -46,6 +47,7 @@ public void FieldRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -74,6 +76,7 @@ public void SerializationRoundtrip_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -109,6 +112,7 @@ public void FieldRoundtripThroughSerialization_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -137,6 +141,7 @@ public void FieldRoundtripThroughSerialization_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -165,6 +170,7 @@ public void Validation_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), @@ -194,6 +200,7 @@ public void CopyConstructor_Works() MonthlyPrice = 0, UpfrontCost = 0, }, + RegulatoryStatus = RegulatoryStatus.Approved, Status = PhoneNumberStatus.Active, Name = "name", NextRenewalDate = DateTimeOffset.Parse("2019-12-27T18:11:19.117Z"), diff --git a/src/Zavudev.Tests/Services/AddressServiceTest.cs b/src/Zavudev.Tests/Services/AddressServiceTest.cs index 6ac263d..78aeb47 100644 --- a/src/Zavudev.Tests/Services/AddressServiceTest.cs +++ b/src/Zavudev.Tests/Services/AddressServiceTest.cs @@ -11,6 +11,8 @@ public async Task Create_Works() new() { CountryCode = "DE", + FirstName = "John", + LastName = "Doe", Locality = "Berlin", PostalCode = "10115", StreetAddress = "123 Main St", diff --git a/src/Zavudev.Tests/Services/PhoneNumberServiceTest.cs b/src/Zavudev.Tests/Services/PhoneNumberServiceTest.cs index 00de373..1775a58 100644 --- a/src/Zavudev.Tests/Services/PhoneNumberServiceTest.cs +++ b/src/Zavudev.Tests/Services/PhoneNumberServiceTest.cs @@ -60,7 +60,7 @@ await this.client.PhoneNumbers.Release( public async Task Requirements_Works() { var response = await this.client.PhoneNumbers.Requirements( - new() { CountryCode = "xx" }, + new(), TestContext.Current.CancellationToken ); response.Validate(); diff --git a/src/Zavudev/Core/ModelBase.cs b/src/Zavudev/Core/ModelBase.cs index 40d3e4f..1db4b4c 100644 --- a/src/Zavudev/Core/ModelBase.cs +++ b/src/Zavudev/Core/ModelBase.cs @@ -94,6 +94,7 @@ protected ModelBase(ModelBase modelBase) new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), + new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), diff --git a/src/Zavudev/Models/Addresses/AddressCreateParams.cs b/src/Zavudev/Models/Addresses/AddressCreateParams.cs index efa1bfc..4f3d881 100644 --- a/src/Zavudev/Models/Addresses/AddressCreateParams.cs +++ b/src/Zavudev/Models/Addresses/AddressCreateParams.cs @@ -10,8 +10,8 @@ namespace Zavudev.Models.Addresses; /// -/// 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`. /// /// NOTE: Do not inherit from this type outside the SDK unless you're okay with /// breaking changes in non-major versions. We may add new methods in the future that @@ -35,6 +35,32 @@ public required string CountryCode init { this._rawBodyData.Set("countryCode", value); } } + /// + /// First name of the person the address is registered to. + /// + public required string FirstName + { + get + { + this._rawBodyData.Freeze(); + return this._rawBodyData.GetNotNullClass("firstName"); + } + init { this._rawBodyData.Set("firstName", value); } + } + + /// + /// Last name of the person the address is registered to. + /// + public required string LastName + { + get + { + this._rawBodyData.Freeze(); + return this._rawBodyData.GetNotNullClass("lastName"); + } + init { this._rawBodyData.Set("lastName", value); } + } + public required string Locality { get @@ -83,6 +109,10 @@ public string? AdministrativeArea } } + /// + /// Business name, when the address belongs to a business. Defaults to the person's + /// full name. + /// public string? BusinessName { get @@ -119,42 +149,6 @@ public string? ExtendedAddress } } - public string? FirstName - { - get - { - this._rawBodyData.Freeze(); - return this._rawBodyData.GetNullableClass("firstName"); - } - init - { - if (value == null) - { - return; - } - - this._rawBodyData.Set("firstName", value); - } - } - - public string? LastName - { - get - { - this._rawBodyData.Freeze(); - return this._rawBodyData.GetNullableClass("lastName"); - } - init - { - if (value == null) - { - return; - } - - this._rawBodyData.Set("lastName", value); - } - } - public AddressCreateParams() { } #pragma warning disable CS8618 diff --git a/src/Zavudev/Models/Addresses/AddressDeleteParams.cs b/src/Zavudev/Models/Addresses/AddressDeleteParams.cs index 3e6a742..7c943fc 100644 --- a/src/Zavudev/Models/Addresses/AddressDeleteParams.cs +++ b/src/Zavudev/Models/Addresses/AddressDeleteParams.cs @@ -9,7 +9,9 @@ namespace Zavudev.Models.Addresses; /// -/// Delete a regulatory address. Cannot delete addresses that are in use. +/// Delete a regulatory address from this project. 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. /// /// NOTE: Do not inherit from this type outside the SDK unless you're okay with /// breaking changes in non-major versions. We may add new methods in the future that diff --git a/src/Zavudev/Models/PhoneNumbers/OwnedPhoneNumber.cs b/src/Zavudev/Models/PhoneNumbers/OwnedPhoneNumber.cs index b8ba0eb..79679eb 100644 --- a/src/Zavudev/Models/PhoneNumbers/OwnedPhoneNumber.cs +++ b/src/Zavudev/Models/PhoneNumbers/OwnedPhoneNumber.cs @@ -6,6 +6,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using Zavudev.Core; +using Zavudev.Exceptions; namespace Zavudev.Models.PhoneNumbers; @@ -68,6 +69,39 @@ public required OwnedPhoneNumberPricing Pricing init { this._rawData.Set("pricing", value); } } + /// + /// 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. + /// + public required ApiEnum RegulatoryStatus + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass>( + "regulatoryStatus" + ); + } + init { this._rawData.Set("regulatoryStatus", value); } + } + + /// + /// 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. + /// public required ApiEnum Status { get @@ -164,6 +198,7 @@ public override void Validate() _ = this.CreatedAt; _ = this.PhoneNumber; this.Pricing.Validate(); + this.RegulatoryStatus.Validate(); this.Status.Validate(); _ = this.Name; _ = this.NextRenewalDate; @@ -207,3 +242,65 @@ class OwnedPhoneNumberFromRaw : IFromRawJson public OwnedPhoneNumber FromRawUnchecked(IReadOnlyDictionary rawData) => OwnedPhoneNumber.FromRawUnchecked(rawData); } + +/// +/// 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. +/// +[JsonConverter(typeof(RegulatoryStatusConverter))] +public enum RegulatoryStatus +{ + Approved, + PendingReview, + Rejected, +} + +sealed class RegulatoryStatusConverter : JsonConverter +{ + public override RegulatoryStatus Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + return JsonSerializer.Deserialize(ref reader, options) switch + { + "approved" => RegulatoryStatus.Approved, + "pending_review" => RegulatoryStatus.PendingReview, + "rejected" => RegulatoryStatus.Rejected, + _ => (RegulatoryStatus)(-1), + }; + } + + public override void Write( + Utf8JsonWriter writer, + RegulatoryStatus value, + JsonSerializerOptions options + ) + { + JsonSerializer.Serialize( + writer, + value switch + { + RegulatoryStatus.Approved => "approved", + RegulatoryStatus.PendingReview => "pending_review", + RegulatoryStatus.Rejected => "rejected", + _ => throw new ZavudevInvalidDataException( + string.Format("Invalid value '{0}' in {1}", value, nameof(value)) + ), + }, + options + ); + } +} diff --git a/src/Zavudev/Models/PhoneNumbers/PhoneNumberPricing.cs b/src/Zavudev/Models/PhoneNumbers/PhoneNumberPricing.cs index e935103..1e1ddbd 100644 --- a/src/Zavudev/Models/PhoneNumbers/PhoneNumberPricing.cs +++ b/src/Zavudev/Models/PhoneNumbers/PhoneNumberPricing.cs @@ -11,9 +11,9 @@ namespace Zavudev.Models.PhoneNumbers; public sealed record class PhoneNumberPricing : JsonModel { /// - /// 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. /// public bool? IsFreeEligible { diff --git a/src/Zavudev/Models/PhoneNumbers/PhoneNumberPurchaseParams.cs b/src/Zavudev/Models/PhoneNumbers/PhoneNumberPurchaseParams.cs index a0e5286..422f0d4 100644 --- a/src/Zavudev/Models/PhoneNumbers/PhoneNumberPurchaseParams.cs +++ b/src/Zavudev/Models/PhoneNumbers/PhoneNumberPurchaseParams.cs @@ -1,21 +1,51 @@ using System; using System.Collections.Frozen; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Net.Http; using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; using Zavudev.Core; namespace Zavudev.Models.PhoneNumbers; /// /// 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. /// /// NOTE: Do not inherit from this type outside the SDK unless you're okay with /// breaking changes in non-major versions. We may add new methods in the future that @@ -63,6 +93,64 @@ public string? Name } } + /// + /// 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. + /// + public IReadOnlyList? RegulatoryRequirements + { + get + { + this._rawBodyData.Freeze(); + return this._rawBodyData.GetNullableStruct>( + "regulatoryRequirements" + ); + } + init + { + if (value == null) + { + return; + } + + this._rawBodyData.Set?>( + "regulatoryRequirements", + value == null ? null : ImmutableArray.ToImmutableArray(value) + ); + } + } + + /// + /// 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. + /// + public ApiEnum? Type + { + get + { + this._rawBodyData.Freeze(); + return this._rawBodyData.GetNullableClass>("type"); + } + init + { + if (value == null) + { + return; + } + + this._rawBodyData.Set("type", value); + } + } + public PhoneNumberPurchaseParams() { } #pragma warning disable CS8618 @@ -172,3 +260,81 @@ public override int GetHashCode() return 0; } } + +[JsonConverter(typeof(JsonModelConverter))] +public sealed record class RegulatoryRequirement : JsonModel +{ + /// + /// 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. + /// + public required string FieldValue + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("fieldValue"); + } + init { this._rawData.Set("fieldValue", value); } + } + + /// + /// A `requirementTypes[].id` from `GET /v1/phone-numbers/requirements`. Each + /// id may appear only once. + /// + public required string RequirementType + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("requirementType"); + } + init { this._rawData.Set("requirementType", value); } + } + + /// + public override void Validate() + { + _ = this.FieldValue; + _ = this.RequirementType; + } + + public RegulatoryRequirement() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public RegulatoryRequirement(RegulatoryRequirement regulatoryRequirement) + : base(regulatoryRequirement) { } +#pragma warning restore CS8618 + + public RegulatoryRequirement(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + RegulatoryRequirement(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static RegulatoryRequirement FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class RegulatoryRequirementFromRaw : IFromRawJson +{ + /// + public RegulatoryRequirement FromRawUnchecked( + IReadOnlyDictionary rawData + ) => RegulatoryRequirement.FromRawUnchecked(rawData); +} diff --git a/src/Zavudev/Models/PhoneNumbers/PhoneNumberRequirementsParams.cs b/src/Zavudev/Models/PhoneNumbers/PhoneNumberRequirementsParams.cs index bc2229f..29338c5 100644 --- a/src/Zavudev/Models/PhoneNumbers/PhoneNumberRequirementsParams.cs +++ b/src/Zavudev/Models/PhoneNumbers/PhoneNumberRequirementsParams.cs @@ -9,9 +9,20 @@ namespace Zavudev.Models.PhoneNumbers; /// -/// 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. /// /// NOTE: Do not inherit from this type outside the SDK unless you're okay with /// breaking changes in non-major versions. We may add new methods in the future that @@ -20,20 +31,53 @@ namespace Zavudev.Models.PhoneNumbers; public record class PhoneNumberRequirementsParams : ParamsBase { /// - /// Two-letter ISO country code. + /// Two-letter ISO country code. Required unless `phoneNumber` is given. /// - public required string CountryCode + public string? CountryCode { get { this._rawQueryData.Freeze(); - return this._rawQueryData.GetNotNullClass("countryCode"); + return this._rawQueryData.GetNullableClass("countryCode"); + } + init + { + if (value == null) + { + return; + } + + this._rawQueryData.Set("countryCode", value); + } + } + + /// + /// 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`. + /// + public string? PhoneNumber + { + get + { + this._rawQueryData.Freeze(); + return this._rawQueryData.GetNullableClass("phoneNumber"); + } + init + { + if (value == null) + { + return; + } + + this._rawQueryData.Set("phoneNumber", value); } - init { this._rawQueryData.Set("countryCode", value); } } /// - /// 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. /// public ApiEnum? Type { diff --git a/src/Zavudev/Models/PhoneNumbers/PhoneNumberStatus.cs b/src/Zavudev/Models/PhoneNumbers/PhoneNumberStatus.cs index 824fad7..7637ada 100644 --- a/src/Zavudev/Models/PhoneNumbers/PhoneNumberStatus.cs +++ b/src/Zavudev/Models/PhoneNumbers/PhoneNumberStatus.cs @@ -5,12 +5,20 @@ namespace Zavudev.Models.PhoneNumbers; +/// +/// 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. +/// [JsonConverter(typeof(PhoneNumberStatusConverter))] public enum PhoneNumberStatus { Active, Suspended, Pending, + Releasing, + Released, } sealed class PhoneNumberStatusConverter : JsonConverter @@ -26,6 +34,8 @@ JsonSerializerOptions options "active" => PhoneNumberStatus.Active, "suspended" => PhoneNumberStatus.Suspended, "pending" => PhoneNumberStatus.Pending, + "releasing" => PhoneNumberStatus.Releasing, + "released" => PhoneNumberStatus.Released, _ => (PhoneNumberStatus)(-1), }; } @@ -43,6 +53,8 @@ JsonSerializerOptions options PhoneNumberStatus.Active => "active", PhoneNumberStatus.Suspended => "suspended", PhoneNumberStatus.Pending => "pending", + PhoneNumberStatus.Releasing => "releasing", + PhoneNumberStatus.Released => "released", _ => throw new ZavudevInvalidDataException( string.Format("Invalid value '{0}' in {1}", value, nameof(value)) ), diff --git a/src/Zavudev/Models/PhoneNumbers/PhoneNumberUpdateParams.cs b/src/Zavudev/Models/PhoneNumbers/PhoneNumberUpdateParams.cs index 5ba796c..8ce36d9 100644 --- a/src/Zavudev/Models/PhoneNumbers/PhoneNumberUpdateParams.cs +++ b/src/Zavudev/Models/PhoneNumbers/PhoneNumberUpdateParams.cs @@ -40,7 +40,9 @@ public string? Name } /// - /// 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. /// public string? SenderID { diff --git a/src/Zavudev/Models/PhoneNumbers/Requirement.cs b/src/Zavudev/Models/PhoneNumbers/Requirement.cs index 39cbd2a..cf598a6 100644 --- a/src/Zavudev/Models/PhoneNumbers/Requirement.cs +++ b/src/Zavudev/Models/PhoneNumbers/Requirement.cs @@ -9,7 +9,9 @@ namespace Zavudev.Models.PhoneNumbers; /// -/// 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). /// [JsonConverter(typeof(JsonModelConverter))] public sealed record class Requirement : JsonModel diff --git a/src/Zavudev/Models/PhoneNumbers/RequirementType.cs b/src/Zavudev/Models/PhoneNumbers/RequirementType.cs index c997e99..6db9129 100644 --- a/src/Zavudev/Models/PhoneNumbers/RequirementType.cs +++ b/src/Zavudev/Models/PhoneNumbers/RequirementType.cs @@ -13,6 +13,9 @@ namespace Zavudev.Models.PhoneNumbers; [JsonConverter(typeof(JsonModelConverter))] public sealed record class RequirementType : JsonModel { + /// + /// Send this as `requirementType` in `regulatoryRequirements` when purchasing. + /// public required string ID { get diff --git a/src/Zavudev/Models/Senders/SenderCreateParams.cs b/src/Zavudev/Models/Senders/SenderCreateParams.cs index 28dee3d..2bdd9ae 100644 --- a/src/Zavudev/Models/Senders/SenderCreateParams.cs +++ b/src/Zavudev/Models/Senders/SenderCreateParams.cs @@ -176,8 +176,10 @@ public bool? EnableVoice /// 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. + /// the project 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. /// public string? PhoneNumber { diff --git a/src/Zavudev/Services/IAddressService.cs b/src/Zavudev/Services/IAddressService.cs index 07f6d5a..27c4619 100644 --- a/src/Zavudev/Services/IAddressService.cs +++ b/src/Zavudev/Services/IAddressService.cs @@ -27,8 +27,9 @@ public interface IAddressService IAddressService WithOptions(Func modifier); /// - /// 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`. /// Task Create( AddressCreateParams parameters, @@ -59,7 +60,9 @@ Task List( ); /// - /// Delete a regulatory address. Cannot delete addresses that are in use. + /// Delete a regulatory address from this project. 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. /// Task Delete(AddressDeleteParams parameters, CancellationToken cancellationToken = default); diff --git a/src/Zavudev/Services/IPhoneNumberService.cs b/src/Zavudev/Services/IPhoneNumberService.cs index 31f4435..bd113e1 100644 --- a/src/Zavudev/Services/IPhoneNumberService.cs +++ b/src/Zavudev/Services/IPhoneNumberService.cs @@ -66,11 +66,41 @@ Task List( /// /// 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. /// Task Purchase( PhoneNumberPurchaseParams parameters, @@ -93,12 +123,24 @@ Task Release( ); /// - /// 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. /// Task Requirements( - PhoneNumberRequirementsParams parameters, + PhoneNumberRequirementsParams? parameters = null, CancellationToken cancellationToken = default ); @@ -192,10 +234,10 @@ Task Release( /// /// Returns a raw HTTP response for get /v1/phone-numbers/requirements, but is otherwise the - /// same as . + /// same as . /// Task> Requirements( - PhoneNumberRequirementsParams parameters, + PhoneNumberRequirementsParams? parameters = null, CancellationToken cancellationToken = default ); diff --git a/src/Zavudev/Services/PhoneNumberService.cs b/src/Zavudev/Services/PhoneNumberService.cs index b1eb811..e94f6fb 100644 --- a/src/Zavudev/Services/PhoneNumberService.cs +++ b/src/Zavudev/Services/PhoneNumberService.cs @@ -130,7 +130,7 @@ await this.Release(parameters with { PhoneNumberID = phoneNumberID }, cancellati /// public async Task Requirements( - PhoneNumberRequirementsParams parameters, + PhoneNumberRequirementsParams? parameters = null, CancellationToken cancellationToken = default ) { @@ -352,10 +352,12 @@ public Task Release( /// public async Task> Requirements( - PhoneNumberRequirementsParams parameters, + PhoneNumberRequirementsParams? parameters = null, CancellationToken cancellationToken = default ) { + parameters ??= new(); + HttpRequest request = new() { Method = HttpMethod.Get, From d1f6cf57b47f3930723f85989cc90bbe2c1a5c5c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 08:47:27 +0000 Subject: [PATCH 03/15] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index bbcd963..52045d0 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-6a4c9cdb8d971155ac22e679a37d27525a97b8a65991908a1c12969252457811.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-c6e6f410e3490fde4e2a5aaca9a4b609856685cfc4b8540c6cef5c2cef097622.yml openapi_spec_hash: 5d999f34f096d592deaa6319f52190b3 -config_hash: 261e1b852ca7f8364e4a283a3edd350a +config_hash: f6a05e2edf2cd1af3f762b8ac759b9a7 From 8d1cf600e13e038e7da9097d0c44ab91f1ce8c48 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:10:20 +0000 Subject: [PATCH 04/15] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 52045d0..bbcd963 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-c6e6f410e3490fde4e2a5aaca9a4b609856685cfc4b8540c6cef5c2cef097622.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-6a4c9cdb8d971155ac22e679a37d27525a97b8a65991908a1c12969252457811.yml openapi_spec_hash: 5d999f34f096d592deaa6319f52190b3 -config_hash: f6a05e2edf2cd1af3f762b8ac759b9a7 +config_hash: 261e1b852ca7f8364e4a283a3edd350a From f26d51fc4dc8e3b1a2a8ae21f4b6dfe7d3c3dab3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:12:18 +0000 Subject: [PATCH 05/15] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index bbcd963..52045d0 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-6a4c9cdb8d971155ac22e679a37d27525a97b8a65991908a1c12969252457811.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-c6e6f410e3490fde4e2a5aaca9a4b609856685cfc4b8540c6cef5c2cef097622.yml openapi_spec_hash: 5d999f34f096d592deaa6319f52190b3 -config_hash: 261e1b852ca7f8364e4a283a3edd350a +config_hash: f6a05e2edf2cd1af3f762b8ac759b9a7 From 01367bbf6af82761f861601a214216e503fdd481 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:21:31 +0000 Subject: [PATCH 06/15] feat(api): api update --- .stats.yml | 4 ++-- src/Zavudev/Models/Senders/WebhookEvent.cs | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.stats.yml b/.stats.yml index 52045d0..0384ef5 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-c6e6f410e3490fde4e2a5aaca9a4b609856685cfc4b8540c6cef5c2cef097622.yml -openapi_spec_hash: 5d999f34f096d592deaa6319f52190b3 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-cf8de13bbc99e47431a3597b98b7a1d7c5161639ed7513c2b7db3e5ea49c4a20.yml +openapi_spec_hash: 4b35637b2a0e3645fc53e8ccc0b1f2de config_hash: f6a05e2edf2cd1af3f762b8ac759b9a7 diff --git a/src/Zavudev/Models/Senders/WebhookEvent.cs b/src/Zavudev/Models/Senders/WebhookEvent.cs index f3103b5..0279052 100644 --- a/src/Zavudev/Models/Senders/WebhookEvent.cs +++ b/src/Zavudev/Models/Senders/WebhookEvent.cs @@ -43,13 +43,18 @@ namespace Zavudev.Models.Senders; /// or `email` key, `channel`, `firstMessageId`, `firstMessageText`, and `profileName`. /// - `template.status_changed`: WhatsApp template approval status changed /// -/// **Partner events:** - `invitation.status_changed`: A partner invitation -/// status changed (pending, in_progress, completed, cancelled, failed). `data` carries -/// `invitationId`, `clientName`, `clientEmail`, `connectionType` (`whatsapp_waba` -/// or `messenger`), `previousStatus`, and `currentStatus`. On `completed` it also -/// carries `senderId` and `connectedAccount` (`channel`, `id`, `name`) — the WhatsApp -/// number or Facebook Page that was linked. On `failed` it carries `failureReason`; -/// the invitation link stays usable, so a client can retry it. +/// **Partner events:** - `invitation.status_changed`: A partner invitation's +/// stored status changed: to `in_progress`, `completed`, `failed`, `cancelled`, or +/// back to `pending` when it is resent from the dashboard. A change to the same status +/// sends nothing, and expiry is not a stored change, so no event is sent when an +/// invitation expires. Delivered to the project webhook (`POST /v1/invitations/webhook`) +/// of the project that created the invitation; a parent project does not receive +/// its sub-accounts' events. `data` carries `invitationId`, `clientName`, `clientEmail`, +/// `connectionType` (`whatsapp_waba` or `messenger`), `previousStatus`, and `currentStatus`. +/// On `completed` it also carries `senderId`, `connectedAccount` (`channel`, `id`, +/// `name`) — the WhatsApp number or Facebook Page that was linked — and, for WhatsApp, +/// `wabaAccountId`. On `failed` it carries `failureReason`; the invitation link +/// stays usable, so a client can retry it. /// /// **Voice Agent events:** For every voice event, `data` carries `callId`, /// `direction`, `from`, `to`, `status`, `durationSeconds`, `endReason`, and `transcriptAvailable`. From 5456b2c8998185dff2f91bf699cc506f32c02864 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 21:57:45 +0000 Subject: [PATCH 07/15] feat(api): api update --- .stats.yml | 4 ++-- src/Zavudev/Models/Senders/Sender.cs | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index 0384ef5..63896e7 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-cf8de13bbc99e47431a3597b98b7a1d7c5161639ed7513c2b7db3e5ea49c4a20.yml -openapi_spec_hash: 4b35637b2a0e3645fc53e8ccc0b1f2de +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-212cea44bc8db5e2be7beb83eafaba27da3ad2d2989b630e05a472e7c91dd6fc.yml +openapi_spec_hash: 965feddccfebb0fc94a7682954a950f0 config_hash: f6a05e2edf2cd1af3f762b8ac759b9a7 diff --git a/src/Zavudev/Models/Senders/Sender.cs b/src/Zavudev/Models/Senders/Sender.cs index d6f9706..c452bc8 100644 --- a/src/Zavudev/Models/Senders/Sender.cs +++ b/src/Zavudev/Models/Senders/Sender.cs @@ -46,10 +46,12 @@ public required string PhoneNumber } /// - /// Channels this sender can actually send on right now, computed from its configuration. + /// 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. Check this rather than inferring capability - /// from phoneNumber or emailAddress. + /// 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`. /// public IReadOnlyList? Channels { From 415d31dbddf1211b0ce0fe66310f5c1e453c2de4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:13:04 +0000 Subject: [PATCH 08/15] feat(api): api update --- .stats.yml | 4 ++-- src/Zavudev/Models/Senders/SenderCreateParams.cs | 6 ++++-- src/Zavudev/Models/Senders/SenderUpdateParams.cs | 5 ++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index 63896e7..1a193ff 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-212cea44bc8db5e2be7beb83eafaba27da3ad2d2989b630e05a472e7c91dd6fc.yml -openapi_spec_hash: 965feddccfebb0fc94a7682954a950f0 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-5257b1c4e3b9259ae2df6cd9ac2d2a31a39ddb336f50baf0dadbcc865c1913f3.yml +openapi_spec_hash: 4103decc37e4053c1cba58ba9527a5e7 config_hash: f6a05e2edf2cd1af3f762b8ac759b9a7 diff --git a/src/Zavudev/Models/Senders/SenderCreateParams.cs b/src/Zavudev/Models/Senders/SenderCreateParams.cs index 2bdd9ae..8d67ab3 100644 --- a/src/Zavudev/Models/Senders/SenderCreateParams.cs +++ b/src/Zavudev/Models/Senders/SenderCreateParams.cs @@ -104,8 +104,10 @@ public string? EmailFromName } /// - /// Enable inbound email receiving on this sender. Requires a verified MX record - /// on the domain; ignored otherwise. + /// 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. /// public bool? EmailReceivingEnabled { diff --git a/src/Zavudev/Models/Senders/SenderUpdateParams.cs b/src/Zavudev/Models/Senders/SenderUpdateParams.cs index 646c795..a448603 100644 --- a/src/Zavudev/Models/Senders/SenderUpdateParams.cs +++ b/src/Zavudev/Models/Senders/SenderUpdateParams.cs @@ -118,7 +118,10 @@ public string? EmailFromName } /// - /// 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. /// public bool? EmailReceivingEnabled { From 0d869f1ea9fd1fc2049483bfd76d6ce8803df627 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:53:30 +0000 Subject: [PATCH 09/15] feat(api): api update --- .stats.yml | 4 ++-- src/Zavudev/Models/Senders/WebhookEvent.cs | 8 +++++++- .../Models/Templates/TemplateSyncParams.cs | 16 ++++++++++++---- .../Models/Templates/TemplateSyncResponse.cs | 3 ++- src/Zavudev/Services/ITemplateService.cs | 15 +++++++++++---- 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/.stats.yml b/.stats.yml index 1a193ff..a3be4c8 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-5257b1c4e3b9259ae2df6cd9ac2d2a31a39ddb336f50baf0dadbcc865c1913f3.yml -openapi_spec_hash: 4103decc37e4053c1cba58ba9527a5e7 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/zavu/zavudev-7ddbc5b54ce2afee5a9c67569f652309a8f79e02643e74af3afba4cb7310f737.yml +openapi_spec_hash: de4fcf9ef758597cfcc59299d12d7b33 config_hash: f6a05e2edf2cd1af3f762b8ac759b9a7 diff --git a/src/Zavudev/Models/Senders/WebhookEvent.cs b/src/Zavudev/Models/Senders/WebhookEvent.cs index 0279052..119d6a2 100644 --- a/src/Zavudev/Models/Senders/WebhookEvent.cs +++ b/src/Zavudev/Models/Senders/WebhookEvent.cs @@ -41,7 +41,13 @@ namespace Zavudev.Models.Senders; /// contact. `data` carries `conversationId` (the inbox thread id — deep-link with /// `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`), the `phoneNumber` /// or `email` key, `channel`, `firstMessageId`, `firstMessageText`, and `profileName`. -/// - `template.status_changed`: WhatsApp template approval status changed +/// - `template.status_changed`: WhatsApp template approval status changed. `data` +/// carries `templateId`, `name`, `previousStatus`, `currentStatus`, `rejectionReason`, +/// and `category` — the category Meta currently bills the template under. Meta can +/// recategorize a template (typically `UTILITY` to `MARKETING`) at approval or long +/// afterwards, which changes what each message costs; `category` is how that reaches +/// you. A recategorization with no status change is delivered as this same event, +/// so compare `category` against what you hold rather than only reacting to `currentStatus`. /// /// **Partner events:** - `invitation.status_changed`: A partner invitation's /// stored status changed: to `in_progress`, `completed`, `failed`, `cancelled`, or diff --git a/src/Zavudev/Models/Templates/TemplateSyncParams.cs b/src/Zavudev/Models/Templates/TemplateSyncParams.cs index c8ac37e..5c7d338 100644 --- a/src/Zavudev/Models/Templates/TemplateSyncParams.cs +++ b/src/Zavudev/Models/Templates/TemplateSyncParams.cs @@ -10,10 +10,18 @@ namespace Zavudev.Models.Templates; /// -/// Reconcile this project's templates against WhatsApp. Two 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. +/// Reconcile this project's templates against WhatsApp. 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), 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 diff --git a/src/Zavudev/Models/Templates/TemplateSyncResponse.cs b/src/Zavudev/Models/Templates/TemplateSyncResponse.cs index 63b698a..b230954 100644 --- a/src/Zavudev/Models/Templates/TemplateSyncResponse.cs +++ b/src/Zavudev/Models/Templates/TemplateSyncResponse.cs @@ -86,7 +86,8 @@ public required long Skipped } /// - /// 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. /// public required long Updated { diff --git a/src/Zavudev/Services/ITemplateService.cs b/src/Zavudev/Services/ITemplateService.cs index fc71bb7..7764c4f 100644 --- a/src/Zavudev/Services/ITemplateService.cs +++ b/src/Zavudev/Services/ITemplateService.cs @@ -88,11 +88,18 @@ Task