From 09ece763faed80422ba22a207234166b6a3f3967 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 11 Jun 2026 11:58:01 +0000
Subject: [PATCH 01/18] chore(api): update composite API spec
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index c60c6d5cbc3..27e912835e9 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 2317
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-6c72aaee26a255cefb59365441066a292ee06cff650529eb8b95d9ffb73ec13a.yml
-openapi_spec_hash: 393731844a065cd2de4b422825007b70
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-6a575ece2abbfbdba8ffcb489f44514843ec30bcc2a55a3a2f029170c32bb1d7.yml
+openapi_spec_hash: 70a294584563b524a9dee76ad2d9ba7e
config_hash: fb00b1d45d83236f53f8ef08adb2db5c
From 74fc77722bf4534f0369c28a79e93be2a0cc3ef8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 4 Jun 2026 17:39:14 +0000
Subject: [PATCH 02/18] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 27e912835e9..b3ededa816d 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 2317
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-6a575ece2abbfbdba8ffcb489f44514843ec30bcc2a55a3a2f029170c32bb1d7.yml
-openapi_spec_hash: 70a294584563b524a9dee76ad2d9ba7e
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-591175684cb8782c27f767af11d14e79135618acb73508c83c20d2d8d090fdac.yml
+openapi_spec_hash: a1cc0b9bd0016b8db503ef9950a12ad8
config_hash: fb00b1d45d83236f53f8ef08adb2db5c
From 019d753c6add15f060bddd12e2a156892cc9cc04 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 4 Jun 2026 18:23:52 +0000
Subject: [PATCH 03/18] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index b3ededa816d..2d3b306983a 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 2317
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-591175684cb8782c27f767af11d14e79135618acb73508c83c20d2d8d090fdac.yml
-openapi_spec_hash: a1cc0b9bd0016b8db503ef9950a12ad8
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-0e70a586436827af2a83ef5365d1de2f32be0c7d777b110ee15d341aa79259fd.yml
+openapi_spec_hash: 37aa89fa53a885c495224e060ad7b3eb
config_hash: fb00b1d45d83236f53f8ef08adb2db5c
From c4f868ed040923d2bf790ac5a909b5677879c8ba Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 4 Jun 2026 19:34:32 +0000
Subject: [PATCH 04/18] chore(api): update composite API spec
---
.stats.yml | 4 +-
.../resources/ai_gateway/ai_gateway.py | 4 ++
.../ai_gateway/ai_gateway_create_response.py | 64 +++++++++++++++++++
.../ai_gateway/ai_gateway_delete_response.py | 64 +++++++++++++++++++
.../ai_gateway/ai_gateway_get_response.py | 64 +++++++++++++++++++
.../ai_gateway/ai_gateway_list_response.py | 64 +++++++++++++++++++
.../ai_gateway/ai_gateway_update_params.py | 64 +++++++++++++++++++
.../ai_gateway/ai_gateway_update_response.py | 64 +++++++++++++++++++
tests/api_resources/test_ai_gateway.py | 32 ++++++++++
9 files changed, 422 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 2d3b306983a..8e63e32d6a4 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 2317
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-0e70a586436827af2a83ef5365d1de2f32be0c7d777b110ee15d341aa79259fd.yml
-openapi_spec_hash: 37aa89fa53a885c495224e060ad7b3eb
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-54bd86a0d11e5777fbe6c4fb8730c59b58bd1e2c0ac834ac060c9a25c09fc7e9.yml
+openapi_spec_hash: 4ac9d21f9fad202efc68bda8af37bdbf
config_hash: fb00b1d45d83236f53f8ef08adb2db5c
diff --git a/src/cloudflare/resources/ai_gateway/ai_gateway.py b/src/cloudflare/resources/ai_gateway/ai_gateway.py
index fac8e0b1718..a2b415a26b5 100644
--- a/src/cloudflare/resources/ai_gateway/ai_gateway.py
+++ b/src/cloudflare/resources/ai_gateway/ai_gateway.py
@@ -255,6 +255,7 @@ def update(
retry_backoff: Optional[Literal["constant", "linear", "exponential"]] | Omit = omit,
retry_delay: Optional[int] | Omit = omit,
retry_max_attempts: Optional[int] | Omit = omit,
+ spend_limits: Optional[ai_gateway_update_params.SpendLimits] | Omit = omit,
store_id: Optional[str] | Omit = omit,
stripe: Optional[ai_gateway_update_params.Stripe] | Omit = omit,
workers_ai_billing_mode: Literal["postpaid"] | Omit = omit,
@@ -314,6 +315,7 @@ def update(
"retry_backoff": retry_backoff,
"retry_delay": retry_delay,
"retry_max_attempts": retry_max_attempts,
+ "spend_limits": spend_limits,
"store_id": store_id,
"stripe": stripe,
"workers_ai_billing_mode": workers_ai_billing_mode,
@@ -627,6 +629,7 @@ async def update(
retry_backoff: Optional[Literal["constant", "linear", "exponential"]] | Omit = omit,
retry_delay: Optional[int] | Omit = omit,
retry_max_attempts: Optional[int] | Omit = omit,
+ spend_limits: Optional[ai_gateway_update_params.SpendLimits] | Omit = omit,
store_id: Optional[str] | Omit = omit,
stripe: Optional[ai_gateway_update_params.Stripe] | Omit = omit,
workers_ai_billing_mode: Literal["postpaid"] | Omit = omit,
@@ -686,6 +689,7 @@ async def update(
"retry_backoff": retry_backoff,
"retry_delay": retry_delay,
"retry_max_attempts": retry_max_attempts,
+ "spend_limits": spend_limits,
"store_id": store_id,
"stripe": stripe,
"workers_ai_billing_mode": workers_ai_billing_mode,
diff --git a/src/cloudflare/types/ai_gateway/ai_gateway_create_response.py b/src/cloudflare/types/ai_gateway/ai_gateway_create_response.py
index 753c5704050..4e0d891f2f1 100644
--- a/src/cloudflare/types/ai_gateway/ai_gateway_create_response.py
+++ b/src/cloudflare/types/ai_gateway/ai_gateway_create_response.py
@@ -18,6 +18,15 @@
"GuardrailsPrompt",
"GuardrailsResponse",
"Otel",
+ "SpendLimits",
+ "SpendLimitsRule",
+ "SpendLimitsRuleMetadata",
+ "SpendLimitsRuleMetadataMode",
+ "SpendLimitsRuleMetadataUnionMember1",
+ "SpendLimitsRuleModel",
+ "SpendLimitsRuleModelMatch",
+ "SpendLimitsRuleProvider",
+ "SpendLimitsRuleProviderMatch",
"Stripe",
"StripeUsageEvent",
]
@@ -128,6 +137,59 @@ class Otel(BaseModel):
content_type: Optional[Literal["json", "protobuf"]] = None
+class SpendLimitsRuleMetadataMode(BaseModel):
+ mode: Literal["partition"]
+
+
+class SpendLimitsRuleMetadataUnionMember1(BaseModel):
+ mode: Literal["match"]
+
+ value: str
+
+
+SpendLimitsRuleMetadata: TypeAlias = Union[SpendLimitsRuleMetadataMode, SpendLimitsRuleMetadataUnionMember1]
+
+
+class SpendLimitsRuleModelMatch(BaseModel):
+ match: str
+
+
+SpendLimitsRuleModel: TypeAlias = Union[Literal["partition"], SpendLimitsRuleModelMatch]
+
+
+class SpendLimitsRuleProviderMatch(BaseModel):
+ match: str
+
+
+SpendLimitsRuleProvider: TypeAlias = Union[Literal["partition"], SpendLimitsRuleProviderMatch]
+
+
+class SpendLimitsRule(BaseModel):
+ id: str
+
+ limit: float
+
+ limit_type: Literal["cost"] = FieldInfo(alias="limitType")
+
+ window: int
+
+ enabled: Optional[bool] = None
+
+ metadata: Optional[Dict[str, SpendLimitsRuleMetadata]] = None
+
+ model: Optional[SpendLimitsRuleModel] = None
+
+ provider: Optional[SpendLimitsRuleProvider] = None
+
+ technique: Optional[Literal["fixed", "sliding"]] = None
+
+
+class SpendLimits(BaseModel):
+ enabled: Optional[bool] = None
+
+ rules: Optional[List[SpendLimitsRule]] = None
+
+
class StripeUsageEvent(BaseModel):
payload: str
@@ -185,6 +247,8 @@ class AIGatewayCreateResponse(BaseModel):
retry_max_attempts: Optional[int] = None
"""Maximum number of retry attempts for failed requests (1-5)"""
+ spend_limits: Optional[SpendLimits] = None
+
store_id: Optional[str] = None
stripe: Optional[Stripe] = None
diff --git a/src/cloudflare/types/ai_gateway/ai_gateway_delete_response.py b/src/cloudflare/types/ai_gateway/ai_gateway_delete_response.py
index bf676c3ea83..54da47ce5d8 100644
--- a/src/cloudflare/types/ai_gateway/ai_gateway_delete_response.py
+++ b/src/cloudflare/types/ai_gateway/ai_gateway_delete_response.py
@@ -18,6 +18,15 @@
"GuardrailsPrompt",
"GuardrailsResponse",
"Otel",
+ "SpendLimits",
+ "SpendLimitsRule",
+ "SpendLimitsRuleMetadata",
+ "SpendLimitsRuleMetadataMode",
+ "SpendLimitsRuleMetadataUnionMember1",
+ "SpendLimitsRuleModel",
+ "SpendLimitsRuleModelMatch",
+ "SpendLimitsRuleProvider",
+ "SpendLimitsRuleProviderMatch",
"Stripe",
"StripeUsageEvent",
]
@@ -128,6 +137,59 @@ class Otel(BaseModel):
content_type: Optional[Literal["json", "protobuf"]] = None
+class SpendLimitsRuleMetadataMode(BaseModel):
+ mode: Literal["partition"]
+
+
+class SpendLimitsRuleMetadataUnionMember1(BaseModel):
+ mode: Literal["match"]
+
+ value: str
+
+
+SpendLimitsRuleMetadata: TypeAlias = Union[SpendLimitsRuleMetadataMode, SpendLimitsRuleMetadataUnionMember1]
+
+
+class SpendLimitsRuleModelMatch(BaseModel):
+ match: str
+
+
+SpendLimitsRuleModel: TypeAlias = Union[Literal["partition"], SpendLimitsRuleModelMatch]
+
+
+class SpendLimitsRuleProviderMatch(BaseModel):
+ match: str
+
+
+SpendLimitsRuleProvider: TypeAlias = Union[Literal["partition"], SpendLimitsRuleProviderMatch]
+
+
+class SpendLimitsRule(BaseModel):
+ id: str
+
+ limit: float
+
+ limit_type: Literal["cost"] = FieldInfo(alias="limitType")
+
+ window: int
+
+ enabled: Optional[bool] = None
+
+ metadata: Optional[Dict[str, SpendLimitsRuleMetadata]] = None
+
+ model: Optional[SpendLimitsRuleModel] = None
+
+ provider: Optional[SpendLimitsRuleProvider] = None
+
+ technique: Optional[Literal["fixed", "sliding"]] = None
+
+
+class SpendLimits(BaseModel):
+ enabled: Optional[bool] = None
+
+ rules: Optional[List[SpendLimitsRule]] = None
+
+
class StripeUsageEvent(BaseModel):
payload: str
@@ -185,6 +247,8 @@ class AIGatewayDeleteResponse(BaseModel):
retry_max_attempts: Optional[int] = None
"""Maximum number of retry attempts for failed requests (1-5)"""
+ spend_limits: Optional[SpendLimits] = None
+
store_id: Optional[str] = None
stripe: Optional[Stripe] = None
diff --git a/src/cloudflare/types/ai_gateway/ai_gateway_get_response.py b/src/cloudflare/types/ai_gateway/ai_gateway_get_response.py
index 642f2530597..a5748465251 100644
--- a/src/cloudflare/types/ai_gateway/ai_gateway_get_response.py
+++ b/src/cloudflare/types/ai_gateway/ai_gateway_get_response.py
@@ -18,6 +18,15 @@
"GuardrailsPrompt",
"GuardrailsResponse",
"Otel",
+ "SpendLimits",
+ "SpendLimitsRule",
+ "SpendLimitsRuleMetadata",
+ "SpendLimitsRuleMetadataMode",
+ "SpendLimitsRuleMetadataUnionMember1",
+ "SpendLimitsRuleModel",
+ "SpendLimitsRuleModelMatch",
+ "SpendLimitsRuleProvider",
+ "SpendLimitsRuleProviderMatch",
"Stripe",
"StripeUsageEvent",
]
@@ -128,6 +137,59 @@ class Otel(BaseModel):
content_type: Optional[Literal["json", "protobuf"]] = None
+class SpendLimitsRuleMetadataMode(BaseModel):
+ mode: Literal["partition"]
+
+
+class SpendLimitsRuleMetadataUnionMember1(BaseModel):
+ mode: Literal["match"]
+
+ value: str
+
+
+SpendLimitsRuleMetadata: TypeAlias = Union[SpendLimitsRuleMetadataMode, SpendLimitsRuleMetadataUnionMember1]
+
+
+class SpendLimitsRuleModelMatch(BaseModel):
+ match: str
+
+
+SpendLimitsRuleModel: TypeAlias = Union[Literal["partition"], SpendLimitsRuleModelMatch]
+
+
+class SpendLimitsRuleProviderMatch(BaseModel):
+ match: str
+
+
+SpendLimitsRuleProvider: TypeAlias = Union[Literal["partition"], SpendLimitsRuleProviderMatch]
+
+
+class SpendLimitsRule(BaseModel):
+ id: str
+
+ limit: float
+
+ limit_type: Literal["cost"] = FieldInfo(alias="limitType")
+
+ window: int
+
+ enabled: Optional[bool] = None
+
+ metadata: Optional[Dict[str, SpendLimitsRuleMetadata]] = None
+
+ model: Optional[SpendLimitsRuleModel] = None
+
+ provider: Optional[SpendLimitsRuleProvider] = None
+
+ technique: Optional[Literal["fixed", "sliding"]] = None
+
+
+class SpendLimits(BaseModel):
+ enabled: Optional[bool] = None
+
+ rules: Optional[List[SpendLimitsRule]] = None
+
+
class StripeUsageEvent(BaseModel):
payload: str
@@ -185,6 +247,8 @@ class AIGatewayGetResponse(BaseModel):
retry_max_attempts: Optional[int] = None
"""Maximum number of retry attempts for failed requests (1-5)"""
+ spend_limits: Optional[SpendLimits] = None
+
store_id: Optional[str] = None
stripe: Optional[Stripe] = None
diff --git a/src/cloudflare/types/ai_gateway/ai_gateway_list_response.py b/src/cloudflare/types/ai_gateway/ai_gateway_list_response.py
index 06720a66689..2c46212adce 100644
--- a/src/cloudflare/types/ai_gateway/ai_gateway_list_response.py
+++ b/src/cloudflare/types/ai_gateway/ai_gateway_list_response.py
@@ -18,6 +18,15 @@
"GuardrailsPrompt",
"GuardrailsResponse",
"Otel",
+ "SpendLimits",
+ "SpendLimitsRule",
+ "SpendLimitsRuleMetadata",
+ "SpendLimitsRuleMetadataMode",
+ "SpendLimitsRuleMetadataUnionMember1",
+ "SpendLimitsRuleModel",
+ "SpendLimitsRuleModelMatch",
+ "SpendLimitsRuleProvider",
+ "SpendLimitsRuleProviderMatch",
"Stripe",
"StripeUsageEvent",
]
@@ -128,6 +137,59 @@ class Otel(BaseModel):
content_type: Optional[Literal["json", "protobuf"]] = None
+class SpendLimitsRuleMetadataMode(BaseModel):
+ mode: Literal["partition"]
+
+
+class SpendLimitsRuleMetadataUnionMember1(BaseModel):
+ mode: Literal["match"]
+
+ value: str
+
+
+SpendLimitsRuleMetadata: TypeAlias = Union[SpendLimitsRuleMetadataMode, SpendLimitsRuleMetadataUnionMember1]
+
+
+class SpendLimitsRuleModelMatch(BaseModel):
+ match: str
+
+
+SpendLimitsRuleModel: TypeAlias = Union[Literal["partition"], SpendLimitsRuleModelMatch]
+
+
+class SpendLimitsRuleProviderMatch(BaseModel):
+ match: str
+
+
+SpendLimitsRuleProvider: TypeAlias = Union[Literal["partition"], SpendLimitsRuleProviderMatch]
+
+
+class SpendLimitsRule(BaseModel):
+ id: str
+
+ limit: float
+
+ limit_type: Literal["cost"] = FieldInfo(alias="limitType")
+
+ window: int
+
+ enabled: Optional[bool] = None
+
+ metadata: Optional[Dict[str, SpendLimitsRuleMetadata]] = None
+
+ model: Optional[SpendLimitsRuleModel] = None
+
+ provider: Optional[SpendLimitsRuleProvider] = None
+
+ technique: Optional[Literal["fixed", "sliding"]] = None
+
+
+class SpendLimits(BaseModel):
+ enabled: Optional[bool] = None
+
+ rules: Optional[List[SpendLimitsRule]] = None
+
+
class StripeUsageEvent(BaseModel):
payload: str
@@ -185,6 +247,8 @@ class AIGatewayListResponse(BaseModel):
retry_max_attempts: Optional[int] = None
"""Maximum number of retry attempts for failed requests (1-5)"""
+ spend_limits: Optional[SpendLimits] = None
+
store_id: Optional[str] = None
stripe: Optional[Stripe] = None
diff --git a/src/cloudflare/types/ai_gateway/ai_gateway_update_params.py b/src/cloudflare/types/ai_gateway/ai_gateway_update_params.py
index 798b0082c6d..b8e49c4aad1 100644
--- a/src/cloudflare/types/ai_gateway/ai_gateway_update_params.py
+++ b/src/cloudflare/types/ai_gateway/ai_gateway_update_params.py
@@ -18,6 +18,15 @@
"GuardrailsPrompt",
"GuardrailsResponse",
"Otel",
+ "SpendLimits",
+ "SpendLimitsRule",
+ "SpendLimitsRuleMetadata",
+ "SpendLimitsRuleMetadataMode",
+ "SpendLimitsRuleMetadataUnionMember1",
+ "SpendLimitsRuleModel",
+ "SpendLimitsRuleModelMatch",
+ "SpendLimitsRuleProvider",
+ "SpendLimitsRuleProviderMatch",
"Stripe",
"StripeUsageEvent",
]
@@ -63,6 +72,8 @@ class AIGatewayUpdateParams(TypedDict, total=False):
retry_max_attempts: Optional[int]
"""Maximum number of retry attempts for failed requests (1-5)"""
+ spend_limits: Optional[SpendLimits]
+
store_id: Optional[str]
stripe: Optional[Stripe]
@@ -181,6 +192,59 @@ class Otel(TypedDict, total=False):
content_type: Literal["json", "protobuf"]
+class SpendLimitsRuleMetadataMode(TypedDict, total=False):
+ mode: Required[Literal["partition"]]
+
+
+class SpendLimitsRuleMetadataUnionMember1(TypedDict, total=False):
+ mode: Required[Literal["match"]]
+
+ value: Required[str]
+
+
+SpendLimitsRuleMetadata: TypeAlias = Union[SpendLimitsRuleMetadataMode, SpendLimitsRuleMetadataUnionMember1]
+
+
+class SpendLimitsRuleModelMatch(TypedDict, total=False):
+ match: Required[str]
+
+
+SpendLimitsRuleModel: TypeAlias = Union[Literal["partition"], SpendLimitsRuleModelMatch]
+
+
+class SpendLimitsRuleProviderMatch(TypedDict, total=False):
+ match: Required[str]
+
+
+SpendLimitsRuleProvider: TypeAlias = Union[Literal["partition"], SpendLimitsRuleProviderMatch]
+
+
+class SpendLimitsRule(TypedDict, total=False):
+ id: Required[str]
+
+ limit: Required[float]
+
+ limit_type: Required[Annotated[Literal["cost"], PropertyInfo(alias="limitType")]]
+
+ window: Required[int]
+
+ enabled: bool
+
+ metadata: Dict[str, SpendLimitsRuleMetadata]
+
+ model: SpendLimitsRuleModel
+
+ provider: SpendLimitsRuleProvider
+
+ technique: Literal["fixed", "sliding"]
+
+
+class SpendLimits(TypedDict, total=False):
+ enabled: bool
+
+ rules: Iterable[SpendLimitsRule]
+
+
class StripeUsageEvent(TypedDict, total=False):
payload: Required[str]
diff --git a/src/cloudflare/types/ai_gateway/ai_gateway_update_response.py b/src/cloudflare/types/ai_gateway/ai_gateway_update_response.py
index 8d5d502e961..efbcdc4e778 100644
--- a/src/cloudflare/types/ai_gateway/ai_gateway_update_response.py
+++ b/src/cloudflare/types/ai_gateway/ai_gateway_update_response.py
@@ -18,6 +18,15 @@
"GuardrailsPrompt",
"GuardrailsResponse",
"Otel",
+ "SpendLimits",
+ "SpendLimitsRule",
+ "SpendLimitsRuleMetadata",
+ "SpendLimitsRuleMetadataMode",
+ "SpendLimitsRuleMetadataUnionMember1",
+ "SpendLimitsRuleModel",
+ "SpendLimitsRuleModelMatch",
+ "SpendLimitsRuleProvider",
+ "SpendLimitsRuleProviderMatch",
"Stripe",
"StripeUsageEvent",
]
@@ -128,6 +137,59 @@ class Otel(BaseModel):
content_type: Optional[Literal["json", "protobuf"]] = None
+class SpendLimitsRuleMetadataMode(BaseModel):
+ mode: Literal["partition"]
+
+
+class SpendLimitsRuleMetadataUnionMember1(BaseModel):
+ mode: Literal["match"]
+
+ value: str
+
+
+SpendLimitsRuleMetadata: TypeAlias = Union[SpendLimitsRuleMetadataMode, SpendLimitsRuleMetadataUnionMember1]
+
+
+class SpendLimitsRuleModelMatch(BaseModel):
+ match: str
+
+
+SpendLimitsRuleModel: TypeAlias = Union[Literal["partition"], SpendLimitsRuleModelMatch]
+
+
+class SpendLimitsRuleProviderMatch(BaseModel):
+ match: str
+
+
+SpendLimitsRuleProvider: TypeAlias = Union[Literal["partition"], SpendLimitsRuleProviderMatch]
+
+
+class SpendLimitsRule(BaseModel):
+ id: str
+
+ limit: float
+
+ limit_type: Literal["cost"] = FieldInfo(alias="limitType")
+
+ window: int
+
+ enabled: Optional[bool] = None
+
+ metadata: Optional[Dict[str, SpendLimitsRuleMetadata]] = None
+
+ model: Optional[SpendLimitsRuleModel] = None
+
+ provider: Optional[SpendLimitsRuleProvider] = None
+
+ technique: Optional[Literal["fixed", "sliding"]] = None
+
+
+class SpendLimits(BaseModel):
+ enabled: Optional[bool] = None
+
+ rules: Optional[List[SpendLimitsRule]] = None
+
+
class StripeUsageEvent(BaseModel):
payload: str
@@ -185,6 +247,8 @@ class AIGatewayUpdateResponse(BaseModel):
retry_max_attempts: Optional[int] = None
"""Maximum number of retry attempts for failed requests (1-5)"""
+ spend_limits: Optional[SpendLimits] = None
+
store_id: Optional[str] = None
stripe: Optional[Stripe] = None
diff --git a/tests/api_resources/test_ai_gateway.py b/tests/api_resources/test_ai_gateway.py
index 20338d953cc..743cd2eef87 100644
--- a/tests/api_resources/test_ai_gateway.py
+++ b/tests/api_resources/test_ai_gateway.py
@@ -189,6 +189,22 @@ def test_method_update_with_all_params(self, client: Cloudflare) -> None:
retry_backoff="constant",
retry_delay=0,
retry_max_attempts=1,
+ spend_limits={
+ "enabled": True,
+ "rules": [
+ {
+ "id": "x",
+ "limit": 1,
+ "limit_type": "cost",
+ "window": 1,
+ "enabled": True,
+ "metadata": {"foo": {"mode": "partition"}},
+ "model": "partition",
+ "provider": "partition",
+ "technique": "fixed",
+ }
+ ],
+ },
store_id="store_id",
stripe={
"authorization": "authorization",
@@ -574,6 +590,22 @@ async def test_method_update_with_all_params(self, async_client: AsyncCloudflare
retry_backoff="constant",
retry_delay=0,
retry_max_attempts=1,
+ spend_limits={
+ "enabled": True,
+ "rules": [
+ {
+ "id": "x",
+ "limit": 1,
+ "limit_type": "cost",
+ "window": 1,
+ "enabled": True,
+ "metadata": {"foo": {"mode": "partition"}},
+ "model": "partition",
+ "provider": "partition",
+ "technique": "fixed",
+ }
+ ],
+ },
store_id="store_id",
stripe={
"authorization": "authorization",
From a038160d8e584cbd47b1f53090ee6e6e47007bc0 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 4 Jun 2026 19:51:08 +0000
Subject: [PATCH 05/18] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 8e63e32d6a4..0ce4509ba4b 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 2317
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-54bd86a0d11e5777fbe6c4fb8730c59b58bd1e2c0ac834ac060c9a25c09fc7e9.yml
-openapi_spec_hash: 4ac9d21f9fad202efc68bda8af37bdbf
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-75879983ce065369848affeb00373a42df8c23ce3fa3c61a9dfa30a87ec21c37.yml
+openapi_spec_hash: 39aa2a92f81f15eaf1e5c9b4d9f4f507
config_hash: fb00b1d45d83236f53f8ef08adb2db5c
From 6bdc0f83244ff885d01f1eca982b8c26ddaca9e1 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Jun 2026 01:54:11 +0000
Subject: [PATCH 06/18] chore(api): update composite API spec
---
.stats.yml | 4 +--
.../resources/abuse_reports/mitigations.py | 32 ++++++++++++++++---
.../abuse_reports/mitigation_list_params.py | 16 ++++++++--
.../abuse_reports/mitigation_list_response.py | 19 +++++++++--
.../mitigation_review_response.py | 19 +++++++++--
.../ai_gateway/ai_gateway_create_response.py | 24 ++++++--------
.../ai_gateway/ai_gateway_delete_response.py | 24 ++++++--------
.../ai_gateway/ai_gateway_get_response.py | 24 ++++++--------
.../ai_gateway/ai_gateway_list_response.py | 24 ++++++--------
.../ai_gateway/ai_gateway_update_params.py | 24 ++++++--------
.../ai_gateway/ai_gateway_update_response.py | 24 ++++++--------
.../abuse_reports/test_mitigations.py | 4 +--
tests/api_resources/test_ai_gateway.py | 24 ++++++++++----
13 files changed, 156 insertions(+), 106 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 0ce4509ba4b..fb99e0836df 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 2317
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-75879983ce065369848affeb00373a42df8c23ce3fa3c61a9dfa30a87ec21c37.yml
-openapi_spec_hash: 39aa2a92f81f15eaf1e5c9b4d9f4f507
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-2f09abc745ce4a36cd5b3b109017dbb41e38f0024f1e3ec15b1b788569e1cc99.yml
+openapi_spec_hash: 1db3a5eafa648c8011565ea8da5c1e1f
config_hash: fb00b1d45d83236f53f8ef08adb2db5c
diff --git a/src/cloudflare/resources/abuse_reports/mitigations.py b/src/cloudflare/resources/abuse_reports/mitigations.py
index 7286fe6009b..ba7dfc2d728 100644
--- a/src/cloudflare/resources/abuse_reports/mitigations.py
+++ b/src/cloudflare/resources/abuse_reports/mitigations.py
@@ -69,13 +69,25 @@ def list(
| Omit = omit,
status: Literal["pending", "active", "in_review", "cancelled", "removed"] | Omit = omit,
type: Literal[
+ "account_suspend",
+ "copyright_interstitial",
+ "geo_block",
"legal_block",
+ "malware_interstitial",
"misleading_interstitial",
- "phishing_interstitial",
"network_block",
+ "phishing_interstitial",
+ "playfairite_enforce",
+ "r2_takedown_account",
+ "r2_takedown_bucket",
+ "r2_takedown_object",
"rate_limit_cache",
- "account_suspend",
"redirect_video_stream",
+ "registrar_freeze",
+ "registrar_parking",
+ "stream_block_account",
+ "user_suspend",
+ "workers_takedown_by_zone_id",
]
| Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -238,13 +250,25 @@ def list(
| Omit = omit,
status: Literal["pending", "active", "in_review", "cancelled", "removed"] | Omit = omit,
type: Literal[
+ "account_suspend",
+ "copyright_interstitial",
+ "geo_block",
"legal_block",
+ "malware_interstitial",
"misleading_interstitial",
- "phishing_interstitial",
"network_block",
+ "phishing_interstitial",
+ "playfairite_enforce",
+ "r2_takedown_account",
+ "r2_takedown_bucket",
+ "r2_takedown_object",
"rate_limit_cache",
- "account_suspend",
"redirect_video_stream",
+ "registrar_freeze",
+ "registrar_parking",
+ "stream_block_account",
+ "user_suspend",
+ "workers_takedown_by_zone_id",
]
| Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
diff --git a/src/cloudflare/types/abuse_reports/mitigation_list_params.py b/src/cloudflare/types/abuse_reports/mitigation_list_params.py
index 4093a07c4fb..6e0e1472123 100644
--- a/src/cloudflare/types/abuse_reports/mitigation_list_params.py
+++ b/src/cloudflare/types/abuse_reports/mitigation_list_params.py
@@ -41,13 +41,25 @@ class MitigationListParams(TypedDict, total=False):
"""Filter by the status of the mitigation."""
type: Literal[
+ "account_suspend",
+ "copyright_interstitial",
+ "geo_block",
"legal_block",
+ "malware_interstitial",
"misleading_interstitial",
- "phishing_interstitial",
"network_block",
+ "phishing_interstitial",
+ "playfairite_enforce",
+ "r2_takedown_account",
+ "r2_takedown_bucket",
+ "r2_takedown_object",
"rate_limit_cache",
- "account_suspend",
"redirect_video_stream",
+ "registrar_freeze",
+ "registrar_parking",
+ "stream_block_account",
+ "user_suspend",
+ "workers_takedown_by_zone_id",
]
"""Filter by the type of mitigation.
diff --git a/src/cloudflare/types/abuse_reports/mitigation_list_response.py b/src/cloudflare/types/abuse_reports/mitigation_list_response.py
index 3e0f9fd8075..6f2f5983a70 100644
--- a/src/cloudflare/types/abuse_reports/mitigation_list_response.py
+++ b/src/cloudflare/types/abuse_reports/mitigation_list_response.py
@@ -21,20 +21,33 @@ class Mitigation(BaseModel):
entity_id: str
entity_type: Literal["url_pattern", "account", "zone"]
+ """The type of entity targeted by a mitigation."""
status: Literal["pending", "active", "in_review", "cancelled", "removed"]
"""The status of a mitigation"""
type: Literal[
+ "account_suspend",
+ "copyright_interstitial",
+ "geo_block",
"legal_block",
+ "malware_interstitial",
"misleading_interstitial",
- "phishing_interstitial",
"network_block",
+ "phishing_interstitial",
+ "playfairite_enforce",
+ "r2_takedown_account",
+ "r2_takedown_bucket",
+ "r2_takedown_object",
"rate_limit_cache",
- "account_suspend",
"redirect_video_stream",
+ "registrar_freeze",
+ "registrar_parking",
+ "stream_block_account",
+ "user_suspend",
+ "workers_takedown_by_zone_id",
]
- """The type of mitigation"""
+ """The type of mitigation applied to a reported entity."""
class MitigationListResponse(BaseModel):
diff --git a/src/cloudflare/types/abuse_reports/mitigation_review_response.py b/src/cloudflare/types/abuse_reports/mitigation_review_response.py
index 6e83c1f4ad3..7d5cf2e61ae 100644
--- a/src/cloudflare/types/abuse_reports/mitigation_review_response.py
+++ b/src/cloudflare/types/abuse_reports/mitigation_review_response.py
@@ -20,17 +20,30 @@ class MitigationReviewResponse(BaseModel):
entity_id: str
entity_type: Literal["url_pattern", "account", "zone"]
+ """The type of entity targeted by a mitigation."""
status: Literal["pending", "active", "in_review", "cancelled", "removed"]
"""The status of a mitigation"""
type: Literal[
+ "account_suspend",
+ "copyright_interstitial",
+ "geo_block",
"legal_block",
+ "malware_interstitial",
"misleading_interstitial",
- "phishing_interstitial",
"network_block",
+ "phishing_interstitial",
+ "playfairite_enforce",
+ "r2_takedown_account",
+ "r2_takedown_bucket",
+ "r2_takedown_object",
"rate_limit_cache",
- "account_suspend",
"redirect_video_stream",
+ "registrar_freeze",
+ "registrar_parking",
+ "stream_block_account",
+ "user_suspend",
+ "workers_takedown_by_zone_id",
]
- """The type of mitigation"""
+ """The type of mitigation applied to a reported entity."""
diff --git a/src/cloudflare/types/ai_gateway/ai_gateway_create_response.py b/src/cloudflare/types/ai_gateway/ai_gateway_create_response.py
index 4e0d891f2f1..4d5c36e42ec 100644
--- a/src/cloudflare/types/ai_gateway/ai_gateway_create_response.py
+++ b/src/cloudflare/types/ai_gateway/ai_gateway_create_response.py
@@ -24,9 +24,7 @@
"SpendLimitsRuleMetadataMode",
"SpendLimitsRuleMetadataUnionMember1",
"SpendLimitsRuleModel",
- "SpendLimitsRuleModelMatch",
"SpendLimitsRuleProvider",
- "SpendLimitsRuleProviderMatch",
"Stripe",
"StripeUsageEvent",
]
@@ -142,37 +140,35 @@ class SpendLimitsRuleMetadataMode(BaseModel):
class SpendLimitsRuleMetadataUnionMember1(BaseModel):
- mode: Literal["match"]
+ mode: Literal["filter"]
- value: str
+ values: List[str]
SpendLimitsRuleMetadata: TypeAlias = Union[SpendLimitsRuleMetadataMode, SpendLimitsRuleMetadataUnionMember1]
-class SpendLimitsRuleModelMatch(BaseModel):
- match: str
+class SpendLimitsRuleModel(BaseModel):
+ mode: Literal["filter"]
+ values: List[str]
-SpendLimitsRuleModel: TypeAlias = Union[Literal["partition"], SpendLimitsRuleModelMatch]
+class SpendLimitsRuleProvider(BaseModel):
+ mode: Literal["filter"]
-class SpendLimitsRuleProviderMatch(BaseModel):
- match: str
-
-
-SpendLimitsRuleProvider: TypeAlias = Union[Literal["partition"], SpendLimitsRuleProviderMatch]
+ values: List[str]
class SpendLimitsRule(BaseModel):
- id: str
-
limit: float
limit_type: Literal["cost"] = FieldInfo(alias="limitType")
window: int
+ id: Optional[str] = None
+
enabled: Optional[bool] = None
metadata: Optional[Dict[str, SpendLimitsRuleMetadata]] = None
diff --git a/src/cloudflare/types/ai_gateway/ai_gateway_delete_response.py b/src/cloudflare/types/ai_gateway/ai_gateway_delete_response.py
index 54da47ce5d8..3fa277ec5af 100644
--- a/src/cloudflare/types/ai_gateway/ai_gateway_delete_response.py
+++ b/src/cloudflare/types/ai_gateway/ai_gateway_delete_response.py
@@ -24,9 +24,7 @@
"SpendLimitsRuleMetadataMode",
"SpendLimitsRuleMetadataUnionMember1",
"SpendLimitsRuleModel",
- "SpendLimitsRuleModelMatch",
"SpendLimitsRuleProvider",
- "SpendLimitsRuleProviderMatch",
"Stripe",
"StripeUsageEvent",
]
@@ -142,37 +140,35 @@ class SpendLimitsRuleMetadataMode(BaseModel):
class SpendLimitsRuleMetadataUnionMember1(BaseModel):
- mode: Literal["match"]
+ mode: Literal["filter"]
- value: str
+ values: List[str]
SpendLimitsRuleMetadata: TypeAlias = Union[SpendLimitsRuleMetadataMode, SpendLimitsRuleMetadataUnionMember1]
-class SpendLimitsRuleModelMatch(BaseModel):
- match: str
+class SpendLimitsRuleModel(BaseModel):
+ mode: Literal["filter"]
+ values: List[str]
-SpendLimitsRuleModel: TypeAlias = Union[Literal["partition"], SpendLimitsRuleModelMatch]
+class SpendLimitsRuleProvider(BaseModel):
+ mode: Literal["filter"]
-class SpendLimitsRuleProviderMatch(BaseModel):
- match: str
-
-
-SpendLimitsRuleProvider: TypeAlias = Union[Literal["partition"], SpendLimitsRuleProviderMatch]
+ values: List[str]
class SpendLimitsRule(BaseModel):
- id: str
-
limit: float
limit_type: Literal["cost"] = FieldInfo(alias="limitType")
window: int
+ id: Optional[str] = None
+
enabled: Optional[bool] = None
metadata: Optional[Dict[str, SpendLimitsRuleMetadata]] = None
diff --git a/src/cloudflare/types/ai_gateway/ai_gateway_get_response.py b/src/cloudflare/types/ai_gateway/ai_gateway_get_response.py
index a5748465251..8baedb39786 100644
--- a/src/cloudflare/types/ai_gateway/ai_gateway_get_response.py
+++ b/src/cloudflare/types/ai_gateway/ai_gateway_get_response.py
@@ -24,9 +24,7 @@
"SpendLimitsRuleMetadataMode",
"SpendLimitsRuleMetadataUnionMember1",
"SpendLimitsRuleModel",
- "SpendLimitsRuleModelMatch",
"SpendLimitsRuleProvider",
- "SpendLimitsRuleProviderMatch",
"Stripe",
"StripeUsageEvent",
]
@@ -142,37 +140,35 @@ class SpendLimitsRuleMetadataMode(BaseModel):
class SpendLimitsRuleMetadataUnionMember1(BaseModel):
- mode: Literal["match"]
+ mode: Literal["filter"]
- value: str
+ values: List[str]
SpendLimitsRuleMetadata: TypeAlias = Union[SpendLimitsRuleMetadataMode, SpendLimitsRuleMetadataUnionMember1]
-class SpendLimitsRuleModelMatch(BaseModel):
- match: str
+class SpendLimitsRuleModel(BaseModel):
+ mode: Literal["filter"]
+ values: List[str]
-SpendLimitsRuleModel: TypeAlias = Union[Literal["partition"], SpendLimitsRuleModelMatch]
+class SpendLimitsRuleProvider(BaseModel):
+ mode: Literal["filter"]
-class SpendLimitsRuleProviderMatch(BaseModel):
- match: str
-
-
-SpendLimitsRuleProvider: TypeAlias = Union[Literal["partition"], SpendLimitsRuleProviderMatch]
+ values: List[str]
class SpendLimitsRule(BaseModel):
- id: str
-
limit: float
limit_type: Literal["cost"] = FieldInfo(alias="limitType")
window: int
+ id: Optional[str] = None
+
enabled: Optional[bool] = None
metadata: Optional[Dict[str, SpendLimitsRuleMetadata]] = None
diff --git a/src/cloudflare/types/ai_gateway/ai_gateway_list_response.py b/src/cloudflare/types/ai_gateway/ai_gateway_list_response.py
index 2c46212adce..90b0d287777 100644
--- a/src/cloudflare/types/ai_gateway/ai_gateway_list_response.py
+++ b/src/cloudflare/types/ai_gateway/ai_gateway_list_response.py
@@ -24,9 +24,7 @@
"SpendLimitsRuleMetadataMode",
"SpendLimitsRuleMetadataUnionMember1",
"SpendLimitsRuleModel",
- "SpendLimitsRuleModelMatch",
"SpendLimitsRuleProvider",
- "SpendLimitsRuleProviderMatch",
"Stripe",
"StripeUsageEvent",
]
@@ -142,37 +140,35 @@ class SpendLimitsRuleMetadataMode(BaseModel):
class SpendLimitsRuleMetadataUnionMember1(BaseModel):
- mode: Literal["match"]
+ mode: Literal["filter"]
- value: str
+ values: List[str]
SpendLimitsRuleMetadata: TypeAlias = Union[SpendLimitsRuleMetadataMode, SpendLimitsRuleMetadataUnionMember1]
-class SpendLimitsRuleModelMatch(BaseModel):
- match: str
+class SpendLimitsRuleModel(BaseModel):
+ mode: Literal["filter"]
+ values: List[str]
-SpendLimitsRuleModel: TypeAlias = Union[Literal["partition"], SpendLimitsRuleModelMatch]
+class SpendLimitsRuleProvider(BaseModel):
+ mode: Literal["filter"]
-class SpendLimitsRuleProviderMatch(BaseModel):
- match: str
-
-
-SpendLimitsRuleProvider: TypeAlias = Union[Literal["partition"], SpendLimitsRuleProviderMatch]
+ values: List[str]
class SpendLimitsRule(BaseModel):
- id: str
-
limit: float
limit_type: Literal["cost"] = FieldInfo(alias="limitType")
window: int
+ id: Optional[str] = None
+
enabled: Optional[bool] = None
metadata: Optional[Dict[str, SpendLimitsRuleMetadata]] = None
diff --git a/src/cloudflare/types/ai_gateway/ai_gateway_update_params.py b/src/cloudflare/types/ai_gateway/ai_gateway_update_params.py
index b8e49c4aad1..de45bb4501c 100644
--- a/src/cloudflare/types/ai_gateway/ai_gateway_update_params.py
+++ b/src/cloudflare/types/ai_gateway/ai_gateway_update_params.py
@@ -24,9 +24,7 @@
"SpendLimitsRuleMetadataMode",
"SpendLimitsRuleMetadataUnionMember1",
"SpendLimitsRuleModel",
- "SpendLimitsRuleModelMatch",
"SpendLimitsRuleProvider",
- "SpendLimitsRuleProviderMatch",
"Stripe",
"StripeUsageEvent",
]
@@ -197,37 +195,35 @@ class SpendLimitsRuleMetadataMode(TypedDict, total=False):
class SpendLimitsRuleMetadataUnionMember1(TypedDict, total=False):
- mode: Required[Literal["match"]]
+ mode: Required[Literal["filter"]]
- value: Required[str]
+ values: Required[SequenceNotStr[str]]
SpendLimitsRuleMetadata: TypeAlias = Union[SpendLimitsRuleMetadataMode, SpendLimitsRuleMetadataUnionMember1]
-class SpendLimitsRuleModelMatch(TypedDict, total=False):
- match: Required[str]
+class SpendLimitsRuleModel(TypedDict, total=False):
+ mode: Required[Literal["filter"]]
+ values: Required[SequenceNotStr[str]]
-SpendLimitsRuleModel: TypeAlias = Union[Literal["partition"], SpendLimitsRuleModelMatch]
+class SpendLimitsRuleProvider(TypedDict, total=False):
+ mode: Required[Literal["filter"]]
-class SpendLimitsRuleProviderMatch(TypedDict, total=False):
- match: Required[str]
-
-
-SpendLimitsRuleProvider: TypeAlias = Union[Literal["partition"], SpendLimitsRuleProviderMatch]
+ values: Required[SequenceNotStr[str]]
class SpendLimitsRule(TypedDict, total=False):
- id: Required[str]
-
limit: Required[float]
limit_type: Required[Annotated[Literal["cost"], PropertyInfo(alias="limitType")]]
window: Required[int]
+ id: str
+
enabled: bool
metadata: Dict[str, SpendLimitsRuleMetadata]
diff --git a/src/cloudflare/types/ai_gateway/ai_gateway_update_response.py b/src/cloudflare/types/ai_gateway/ai_gateway_update_response.py
index efbcdc4e778..febe03efdb8 100644
--- a/src/cloudflare/types/ai_gateway/ai_gateway_update_response.py
+++ b/src/cloudflare/types/ai_gateway/ai_gateway_update_response.py
@@ -24,9 +24,7 @@
"SpendLimitsRuleMetadataMode",
"SpendLimitsRuleMetadataUnionMember1",
"SpendLimitsRuleModel",
- "SpendLimitsRuleModelMatch",
"SpendLimitsRuleProvider",
- "SpendLimitsRuleProviderMatch",
"Stripe",
"StripeUsageEvent",
]
@@ -142,37 +140,35 @@ class SpendLimitsRuleMetadataMode(BaseModel):
class SpendLimitsRuleMetadataUnionMember1(BaseModel):
- mode: Literal["match"]
+ mode: Literal["filter"]
- value: str
+ values: List[str]
SpendLimitsRuleMetadata: TypeAlias = Union[SpendLimitsRuleMetadataMode, SpendLimitsRuleMetadataUnionMember1]
-class SpendLimitsRuleModelMatch(BaseModel):
- match: str
+class SpendLimitsRuleModel(BaseModel):
+ mode: Literal["filter"]
+ values: List[str]
-SpendLimitsRuleModel: TypeAlias = Union[Literal["partition"], SpendLimitsRuleModelMatch]
+class SpendLimitsRuleProvider(BaseModel):
+ mode: Literal["filter"]
-class SpendLimitsRuleProviderMatch(BaseModel):
- match: str
-
-
-SpendLimitsRuleProvider: TypeAlias = Union[Literal["partition"], SpendLimitsRuleProviderMatch]
+ values: List[str]
class SpendLimitsRule(BaseModel):
- id: str
-
limit: float
limit_type: Literal["cost"] = FieldInfo(alias="limitType")
window: int
+ id: Optional[str] = None
+
enabled: Optional[bool] = None
metadata: Optional[Dict[str, SpendLimitsRuleMetadata]] = None
diff --git a/tests/api_resources/abuse_reports/test_mitigations.py b/tests/api_resources/abuse_reports/test_mitigations.py
index 7975ce3809e..8fff01ce149 100644
--- a/tests/api_resources/abuse_reports/test_mitigations.py
+++ b/tests/api_resources/abuse_reports/test_mitigations.py
@@ -43,7 +43,7 @@ def test_method_list_with_all_params(self, client: Cloudflare) -> None:
per_page=0,
sort="type,asc",
status="pending",
- type="legal_block",
+ type="account_suspend",
)
assert_matches_type(SyncV4PagePagination[Optional[MitigationListResponse]], mitigation, path=["response"])
@@ -200,7 +200,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCloudflare)
per_page=0,
sort="type,asc",
status="pending",
- type="legal_block",
+ type="account_suspend",
)
assert_matches_type(AsyncV4PagePagination[Optional[MitigationListResponse]], mitigation, path=["response"])
diff --git a/tests/api_resources/test_ai_gateway.py b/tests/api_resources/test_ai_gateway.py
index 743cd2eef87..33015cc2c13 100644
--- a/tests/api_resources/test_ai_gateway.py
+++ b/tests/api_resources/test_ai_gateway.py
@@ -193,14 +193,20 @@ def test_method_update_with_all_params(self, client: Cloudflare) -> None:
"enabled": True,
"rules": [
{
- "id": "x",
"limit": 1,
"limit_type": "cost",
"window": 1,
+ "id": "x",
"enabled": True,
"metadata": {"foo": {"mode": "partition"}},
- "model": "partition",
- "provider": "partition",
+ "model": {
+ "mode": "filter",
+ "values": ["string"],
+ },
+ "provider": {
+ "mode": "filter",
+ "values": ["string"],
+ },
"technique": "fixed",
}
],
@@ -594,14 +600,20 @@ async def test_method_update_with_all_params(self, async_client: AsyncCloudflare
"enabled": True,
"rules": [
{
- "id": "x",
"limit": 1,
"limit_type": "cost",
"window": 1,
+ "id": "x",
"enabled": True,
"metadata": {"foo": {"mode": "partition"}},
- "model": "partition",
- "provider": "partition",
+ "model": {
+ "mode": "filter",
+ "values": ["string"],
+ },
+ "provider": {
+ "mode": "filter",
+ "values": ["string"],
+ },
"technique": "fixed",
}
],
From ea8bb2365e76ffe7949d98b462b70889907adbda Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Jun 2026 09:28:21 +0000
Subject: [PATCH 07/18] chore(api): update composite API spec
---
.stats.yml | 4 ++--
.../resources/realtime_kit/sessions.py | 12 ++++--------
...n_get_participant_data_from_peer_id_params.py | 5 +----
...get_participant_data_from_peer_id_response.py | 16 ++++++++++------
4 files changed, 17 insertions(+), 20 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index fb99e0836df..758e269154a 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 2317
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-2f09abc745ce4a36cd5b3b109017dbb41e38f0024f1e3ec15b1b788569e1cc99.yml
-openapi_spec_hash: 1db3a5eafa648c8011565ea8da5c1e1f
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-07646f4c65fe7a5912f7169b0a633e003b82beb7e70ed5a9a7c1da5d6010b43d.yml
+openapi_spec_hash: bdc110c6261f487424b970a4e61bff5a
config_hash: fb00b1d45d83236f53f8ef08adb2db5c
diff --git a/src/cloudflare/resources/realtime_kit/sessions.py b/src/cloudflare/resources/realtime_kit/sessions.py
index 5c575c80543..749342eb2ed 100644
--- a/src/cloudflare/resources/realtime_kit/sessions.py
+++ b/src/cloudflare/resources/realtime_kit/sessions.py
@@ -131,16 +131,14 @@ def get_participant_data_from_peer_id(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SessionGetParticipantDataFromPeerIDResponse:
"""
- Returns details of the given peer ID along with call statistics for the given
- session ID.
+ Returns participant details for the given peer ID along with call statistics.
Args:
account_id: The account identifier tag.
app_id: The app identifier tag.
- filters: Comma separated list of filters to apply. Note that there must be no spaces
- between the filters.
+ filters: Filter to apply to the peer report.
include_peer_events: if true, response includes all the peer events of participant.
@@ -718,16 +716,14 @@ async def get_participant_data_from_peer_id(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SessionGetParticipantDataFromPeerIDResponse:
"""
- Returns details of the given peer ID along with call statistics for the given
- session ID.
+ Returns participant details for the given peer ID along with call statistics.
Args:
account_id: The account identifier tag.
app_id: The app identifier tag.
- filters: Comma separated list of filters to apply. Note that there must be no spaces
- between the filters.
+ filters: Filter to apply to the peer report.
include_peer_events: if true, response includes all the peer events of participant.
diff --git a/src/cloudflare/types/realtime_kit/session_get_participant_data_from_peer_id_params.py b/src/cloudflare/types/realtime_kit/session_get_participant_data_from_peer_id_params.py
index 95288d3835c..d36ef09e63c 100644
--- a/src/cloudflare/types/realtime_kit/session_get_participant_data_from_peer_id_params.py
+++ b/src/cloudflare/types/realtime_kit/session_get_participant_data_from_peer_id_params.py
@@ -15,10 +15,7 @@ class SessionGetParticipantDataFromPeerIDParams(TypedDict, total=False):
"""The app identifier tag."""
filters: Literal["device_info", "ip_information", "precall_network_information", "events", "quality_stats"]
- """Comma separated list of filters to apply.
-
- Note that there must be no spaces between the filters.
- """
+ """Filter to apply to the peer report."""
include_peer_events: bool
"""if true, response includes all the peer events of participant."""
diff --git a/src/cloudflare/types/realtime_kit/session_get_participant_data_from_peer_id_response.py b/src/cloudflare/types/realtime_kit/session_get_participant_data_from_peer_id_response.py
index 1ac3743888b..763cd63ba8b 100644
--- a/src/cloudflare/types/realtime_kit/session_get_participant_data_from_peer_id_response.py
+++ b/src/cloudflare/types/realtime_kit/session_get_participant_data_from_peer_id_response.py
@@ -6,10 +6,10 @@
from ..._models import BaseModel
-__all__ = ["SessionGetParticipantDataFromPeerIDResponse", "Data", "DataPeerReport"]
+__all__ = ["SessionGetParticipantDataFromPeerIDResponse", "Data", "DataParticipant", "DataParticipantPeerReport"]
-class DataPeerReport(BaseModel):
+class DataParticipantPeerReport(BaseModel):
"""Peer call statistics report."""
metadata: Optional[Dict[str, object]] = None
@@ -29,9 +29,9 @@ def __getattr__(self, attr: str) -> object: ...
__pydantic_extra__: Dict[str, object]
-class Data(BaseModel):
+class DataParticipant(BaseModel):
id: Optional[str] = None
- """Participant ID. This maps to the corresponding peerId."""
+ """ID of the participant."""
created_at: Optional[str] = None
"""timestamp when this participant was created."""
@@ -53,10 +53,10 @@ class Data(BaseModel):
peer_events: Optional[List[Dict[str, object]]] = None
- peer_report: Optional[DataPeerReport] = None
+ peer_report: Optional[DataParticipantPeerReport] = None
"""Peer call statistics report."""
- preset_name: Optional[str] = None
+ role: Optional[str] = None
"""Name of the preset associated with the participant."""
session_id: Optional[str] = None
@@ -68,6 +68,10 @@ class Data(BaseModel):
"""User id for this participant."""
+class Data(BaseModel):
+ participant: Optional[DataParticipant] = None
+
+
class SessionGetParticipantDataFromPeerIDResponse(BaseModel):
data: Optional[Data] = None
From 615120d8945a1ace082539c211c57c6333fcd4e0 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Jun 2026 09:38:14 +0000
Subject: [PATCH 08/18] chore(api): update composite API spec
---
.stats.yml | 4 ++--
src/cloudflare/resources/logpush/datasets/fields.py | 2 ++
src/cloudflare/resources/logpush/datasets/jobs.py | 2 ++
src/cloudflare/resources/logpush/jobs.py | 2 ++
src/cloudflare/types/logpush/job_create_params.py | 1 +
src/cloudflare/types/logpush/logpush_job.py | 1 +
6 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 758e269154a..242003848c8 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 2317
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-07646f4c65fe7a5912f7169b0a633e003b82beb7e70ed5a9a7c1da5d6010b43d.yml
-openapi_spec_hash: bdc110c6261f487424b970a4e61bff5a
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-53b0b78bd2fb754d2e8c41c2c257d96ab676b63eb2282e915c32e5d8ec2b54c9.yml
+openapi_spec_hash: fdf1f031f4955b861d698a110460d29e
config_hash: fb00b1d45d83236f53f8ef08adb2db5c
diff --git a/src/cloudflare/resources/logpush/datasets/fields.py b/src/cloudflare/resources/logpush/datasets/fields.py
index b833e0cd1fc..905b3f0fcb0 100644
--- a/src/cloudflare/resources/logpush/datasets/fields.py
+++ b/src/cloudflare/resources/logpush/datasets/fields.py
@@ -74,6 +74,7 @@ def get(
"sinkhole_http_logs",
"spectrum_events",
"ssh_logs",
+ "turnstile_events",
"warp_config_changes",
"warp_toggle_changes",
"workers_trace_events",
@@ -194,6 +195,7 @@ async def get(
"sinkhole_http_logs",
"spectrum_events",
"ssh_logs",
+ "turnstile_events",
"warp_config_changes",
"warp_toggle_changes",
"workers_trace_events",
diff --git a/src/cloudflare/resources/logpush/datasets/jobs.py b/src/cloudflare/resources/logpush/datasets/jobs.py
index ece4498669f..d229ba4c636 100644
--- a/src/cloudflare/resources/logpush/datasets/jobs.py
+++ b/src/cloudflare/resources/logpush/datasets/jobs.py
@@ -75,6 +75,7 @@ def get(
"sinkhole_http_logs",
"spectrum_events",
"ssh_logs",
+ "turnstile_events",
"warp_config_changes",
"warp_toggle_changes",
"workers_trace_events",
@@ -190,6 +191,7 @@ def get(
"sinkhole_http_logs",
"spectrum_events",
"ssh_logs",
+ "turnstile_events",
"warp_config_changes",
"warp_toggle_changes",
"workers_trace_events",
diff --git a/src/cloudflare/resources/logpush/jobs.py b/src/cloudflare/resources/logpush/jobs.py
index 67ed788882f..8c62a706d68 100644
--- a/src/cloudflare/resources/logpush/jobs.py
+++ b/src/cloudflare/resources/logpush/jobs.py
@@ -84,6 +84,7 @@ def create(
"sinkhole_http_logs",
"spectrum_events",
"ssh_logs",
+ "turnstile_events",
"warp_config_changes",
"warp_toggle_changes",
"workers_trace_events",
@@ -590,6 +591,7 @@ async def create(
"sinkhole_http_logs",
"spectrum_events",
"ssh_logs",
+ "turnstile_events",
"warp_config_changes",
"warp_toggle_changes",
"workers_trace_events",
diff --git a/src/cloudflare/types/logpush/job_create_params.py b/src/cloudflare/types/logpush/job_create_params.py
index ca59411a9df..61d6089a4d6 100644
--- a/src/cloudflare/types/logpush/job_create_params.py
+++ b/src/cloudflare/types/logpush/job_create_params.py
@@ -54,6 +54,7 @@ class JobCreateParams(TypedDict, total=False):
"sinkhole_http_logs",
"spectrum_events",
"ssh_logs",
+ "turnstile_events",
"warp_config_changes",
"warp_toggle_changes",
"workers_trace_events",
diff --git a/src/cloudflare/types/logpush/logpush_job.py b/src/cloudflare/types/logpush/logpush_job.py
index e20ceeab1cb..fbe866558cc 100644
--- a/src/cloudflare/types/logpush/logpush_job.py
+++ b/src/cloudflare/types/logpush/logpush_job.py
@@ -44,6 +44,7 @@ class LogpushJob(BaseModel):
"sinkhole_http_logs",
"spectrum_events",
"ssh_logs",
+ "turnstile_events",
"warp_config_changes",
"warp_toggle_changes",
"workers_trace_events",
From 8e90aea5ec02dbac13135562a8669911bb55928d Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Jun 2026 10:14:34 +0000
Subject: [PATCH 09/18] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 242003848c8..d3299905b46 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 2317
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-53b0b78bd2fb754d2e8c41c2c257d96ab676b63eb2282e915c32e5d8ec2b54c9.yml
-openapi_spec_hash: fdf1f031f4955b861d698a110460d29e
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-9b97be8ae999d02a73e0e2e4c6756b0b89db362800b3e37aaf84dc355ea50238.yml
+openapi_spec_hash: 9f044e790240c1256984f011c9b6539b
config_hash: fb00b1d45d83236f53f8ef08adb2db5c
From 7e5da92440b525eaff14d05cba1b5f099373508a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Jun 2026 13:49:46 +0000
Subject: [PATCH 10/18] feat: fix(dns): Exclude dns_records From
combineCloudflareResources in Prod Config (APIX-981)
* fix(dns): Exclude dns_records From combineCloudflareResources in Prod Config (APIX-981)
---
.stats.yml | 6 +-
src/cloudflare/resources/dns/__init__.py | 14 ++
src/cloudflare/resources/dns/api.md | 26 +++
src/cloudflare/resources/dns/dns.py | 32 +++
.../resources/dns/usage/__init__.py | 47 +++++
src/cloudflare/resources/dns/usage/account.py | 183 ++++++++++++++++++
src/cloudflare/resources/dns/usage/usage.py | 134 +++++++++++++
src/cloudflare/resources/dns/usage/zone.py | 181 +++++++++++++++++
src/cloudflare/types/dns/usage/__init__.py | 3 +
.../types/dns/usage/account_get_response.py | 30 +++
.../types/dns/usage/zone_get_response.py | 18 ++
tests/api_resources/dns/usage/__init__.py | 1 +
tests/api_resources/dns/usage/test_account.py | 100 ++++++++++
tests/api_resources/dns/usage/test_zone.py | 100 ++++++++++
14 files changed, 872 insertions(+), 3 deletions(-)
create mode 100644 src/cloudflare/resources/dns/usage/__init__.py
create mode 100644 src/cloudflare/resources/dns/usage/account.py
create mode 100644 src/cloudflare/resources/dns/usage/usage.py
create mode 100644 src/cloudflare/resources/dns/usage/zone.py
create mode 100644 src/cloudflare/types/dns/usage/account_get_response.py
create mode 100644 src/cloudflare/types/dns/usage/zone_get_response.py
create mode 100644 tests/api_resources/dns/usage/__init__.py
create mode 100644 tests/api_resources/dns/usage/test_account.py
create mode 100644 tests/api_resources/dns/usage/test_zone.py
diff --git a/.stats.yml b/.stats.yml
index d3299905b46..c11620ea6f2 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 2317
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-9b97be8ae999d02a73e0e2e4c6756b0b89db362800b3e37aaf84dc355ea50238.yml
+configured_endpoints: 2319
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-95e73b4de689baa5f103f6b57f8b0c16e7a2380a5a707cbf8c35c33537adba54.yml
openapi_spec_hash: 9f044e790240c1256984f011c9b6539b
-config_hash: fb00b1d45d83236f53f8ef08adb2db5c
+config_hash: 3d78de7487c424794f10d37798c7cf95
diff --git a/src/cloudflare/resources/dns/__init__.py b/src/cloudflare/resources/dns/__init__.py
index 58768dba906..7d175c52cab 100644
--- a/src/cloudflare/resources/dns/__init__.py
+++ b/src/cloudflare/resources/dns/__init__.py
@@ -8,6 +8,14 @@
DNSResourceWithStreamingResponse,
AsyncDNSResourceWithStreamingResponse,
)
+from .usage import (
+ UsageResource,
+ AsyncUsageResource,
+ UsageResourceWithRawResponse,
+ AsyncUsageResourceWithRawResponse,
+ UsageResourceWithStreamingResponse,
+ AsyncUsageResourceWithStreamingResponse,
+)
from .dnssec import (
DNSSECResource,
AsyncDNSSECResource,
@@ -62,6 +70,12 @@
"AsyncRecordsResourceWithRawResponse",
"RecordsResourceWithStreamingResponse",
"AsyncRecordsResourceWithStreamingResponse",
+ "UsageResource",
+ "AsyncUsageResource",
+ "UsageResourceWithRawResponse",
+ "AsyncUsageResourceWithRawResponse",
+ "UsageResourceWithStreamingResponse",
+ "AsyncUsageResourceWithStreamingResponse",
"SettingsResource",
"AsyncSettingsResource",
"SettingsResourceWithRawResponse",
diff --git a/src/cloudflare/resources/dns/api.md b/src/cloudflare/resources/dns/api.md
index 73fb955aeed..8695bc1b784 100644
--- a/src/cloudflare/resources/dns/api.md
+++ b/src/cloudflare/resources/dns/api.md
@@ -72,6 +72,32 @@ Methods:
- client.dns.records.scan_review(\*, zone_id, \*\*params) -> Optional[RecordScanReviewResponse]
- client.dns.records.scan_trigger(\*, zone_id) -> RecordScanTriggerResponse
+## Usage
+
+### Zone
+
+Types:
+
+```python
+from cloudflare.types.dns.usage import ZoneGetResponse
+```
+
+Methods:
+
+- client.dns.usage.zone.get(\*, zone_id) -> Optional[ZoneGetResponse]
+
+### Account
+
+Types:
+
+```python
+from cloudflare.types.dns.usage import AccountGetResponse
+```
+
+Methods:
+
+- client.dns.usage.account.get(\*, account_id) -> Optional[AccountGetResponse]
+
## Settings
### Zone
diff --git a/src/cloudflare/resources/dns/dns.py b/src/cloudflare/resources/dns/dns.py
index a4c4157a412..badc953563b 100644
--- a/src/cloudflare/resources/dns/dns.py
+++ b/src/cloudflare/resources/dns/dns.py
@@ -20,6 +20,14 @@
)
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
+from .usage.usage import (
+ UsageResource,
+ AsyncUsageResource,
+ UsageResourceWithRawResponse,
+ AsyncUsageResourceWithRawResponse,
+ UsageResourceWithStreamingResponse,
+ AsyncUsageResourceWithStreamingResponse,
+)
from .settings.settings import (
SettingsResource,
AsyncSettingsResource,
@@ -57,6 +65,10 @@ def dnssec(self) -> DNSSECResource:
def records(self) -> RecordsResource:
return RecordsResource(self._client)
+ @cached_property
+ def usage(self) -> UsageResource:
+ return UsageResource(self._client)
+
@cached_property
def settings(self) -> SettingsResource:
return SettingsResource(self._client)
@@ -98,6 +110,10 @@ def dnssec(self) -> AsyncDNSSECResource:
def records(self) -> AsyncRecordsResource:
return AsyncRecordsResource(self._client)
+ @cached_property
+ def usage(self) -> AsyncUsageResource:
+ return AsyncUsageResource(self._client)
+
@cached_property
def settings(self) -> AsyncSettingsResource:
return AsyncSettingsResource(self._client)
@@ -142,6 +158,10 @@ def dnssec(self) -> DNSSECResourceWithRawResponse:
def records(self) -> RecordsResourceWithRawResponse:
return RecordsResourceWithRawResponse(self._dns.records)
+ @cached_property
+ def usage(self) -> UsageResourceWithRawResponse:
+ return UsageResourceWithRawResponse(self._dns.usage)
+
@cached_property
def settings(self) -> SettingsResourceWithRawResponse:
return SettingsResourceWithRawResponse(self._dns.settings)
@@ -167,6 +187,10 @@ def dnssec(self) -> AsyncDNSSECResourceWithRawResponse:
def records(self) -> AsyncRecordsResourceWithRawResponse:
return AsyncRecordsResourceWithRawResponse(self._dns.records)
+ @cached_property
+ def usage(self) -> AsyncUsageResourceWithRawResponse:
+ return AsyncUsageResourceWithRawResponse(self._dns.usage)
+
@cached_property
def settings(self) -> AsyncSettingsResourceWithRawResponse:
return AsyncSettingsResourceWithRawResponse(self._dns.settings)
@@ -192,6 +216,10 @@ def dnssec(self) -> DNSSECResourceWithStreamingResponse:
def records(self) -> RecordsResourceWithStreamingResponse:
return RecordsResourceWithStreamingResponse(self._dns.records)
+ @cached_property
+ def usage(self) -> UsageResourceWithStreamingResponse:
+ return UsageResourceWithStreamingResponse(self._dns.usage)
+
@cached_property
def settings(self) -> SettingsResourceWithStreamingResponse:
return SettingsResourceWithStreamingResponse(self._dns.settings)
@@ -217,6 +245,10 @@ def dnssec(self) -> AsyncDNSSECResourceWithStreamingResponse:
def records(self) -> AsyncRecordsResourceWithStreamingResponse:
return AsyncRecordsResourceWithStreamingResponse(self._dns.records)
+ @cached_property
+ def usage(self) -> AsyncUsageResourceWithStreamingResponse:
+ return AsyncUsageResourceWithStreamingResponse(self._dns.usage)
+
@cached_property
def settings(self) -> AsyncSettingsResourceWithStreamingResponse:
return AsyncSettingsResourceWithStreamingResponse(self._dns.settings)
diff --git a/src/cloudflare/resources/dns/usage/__init__.py b/src/cloudflare/resources/dns/usage/__init__.py
new file mode 100644
index 00000000000..84f2ba47257
--- /dev/null
+++ b/src/cloudflare/resources/dns/usage/__init__.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .zone import (
+ ZoneResource,
+ AsyncZoneResource,
+ ZoneResourceWithRawResponse,
+ AsyncZoneResourceWithRawResponse,
+ ZoneResourceWithStreamingResponse,
+ AsyncZoneResourceWithStreamingResponse,
+)
+from .usage import (
+ UsageResource,
+ AsyncUsageResource,
+ UsageResourceWithRawResponse,
+ AsyncUsageResourceWithRawResponse,
+ UsageResourceWithStreamingResponse,
+ AsyncUsageResourceWithStreamingResponse,
+)
+from .account import (
+ AccountResource,
+ AsyncAccountResource,
+ AccountResourceWithRawResponse,
+ AsyncAccountResourceWithRawResponse,
+ AccountResourceWithStreamingResponse,
+ AsyncAccountResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "ZoneResource",
+ "AsyncZoneResource",
+ "ZoneResourceWithRawResponse",
+ "AsyncZoneResourceWithRawResponse",
+ "ZoneResourceWithStreamingResponse",
+ "AsyncZoneResourceWithStreamingResponse",
+ "AccountResource",
+ "AsyncAccountResource",
+ "AccountResourceWithRawResponse",
+ "AsyncAccountResourceWithRawResponse",
+ "AccountResourceWithStreamingResponse",
+ "AsyncAccountResourceWithStreamingResponse",
+ "UsageResource",
+ "AsyncUsageResource",
+ "UsageResourceWithRawResponse",
+ "AsyncUsageResourceWithRawResponse",
+ "UsageResourceWithStreamingResponse",
+ "AsyncUsageResourceWithStreamingResponse",
+]
diff --git a/src/cloudflare/resources/dns/usage/account.py b/src/cloudflare/resources/dns/usage/account.py
new file mode 100644
index 00000000000..b53b9e4109d
--- /dev/null
+++ b/src/cloudflare/resources/dns/usage/account.py
@@ -0,0 +1,183 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Type, Optional, cast
+
+import httpx
+
+from ...._types import Body, Query, Headers, NotGiven, not_given
+from ...._utils import path_template
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...._wrappers import ResultWrapper
+from ...._base_client import make_request_options
+from ....types.dns.usage.account_get_response import AccountGetResponse
+
+__all__ = ["AccountResource", "AsyncAccountResource"]
+
+
+class AccountResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AccountResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#accessing-raw-response-data-eg-headers
+ """
+ return AccountResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AccountResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#with_streaming_response
+ """
+ return AccountResourceWithStreamingResponse(self)
+
+ def get(
+ self,
+ *,
+ account_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[AccountGetResponse]:
+ """Get the current DNS record usage and quota for an account.
+
+ May include internal
+ DNS usage and quota.
+
+ Args:
+ account_id: Identifier.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ return self._get(
+ path_template("/accounts/{account_id}/dns_records/usage", account_id=account_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[AccountGetResponse]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[AccountGetResponse]], ResultWrapper[AccountGetResponse]),
+ )
+
+
+class AsyncAccountResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncAccountResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncAccountResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncAccountResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#with_streaming_response
+ """
+ return AsyncAccountResourceWithStreamingResponse(self)
+
+ async def get(
+ self,
+ *,
+ account_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[AccountGetResponse]:
+ """Get the current DNS record usage and quota for an account.
+
+ May include internal
+ DNS usage and quota.
+
+ Args:
+ account_id: Identifier.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ return await self._get(
+ path_template("/accounts/{account_id}/dns_records/usage", account_id=account_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[AccountGetResponse]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[AccountGetResponse]], ResultWrapper[AccountGetResponse]),
+ )
+
+
+class AccountResourceWithRawResponse:
+ def __init__(self, account: AccountResource) -> None:
+ self._account = account
+
+ self.get = to_raw_response_wrapper(
+ account.get,
+ )
+
+
+class AsyncAccountResourceWithRawResponse:
+ def __init__(self, account: AsyncAccountResource) -> None:
+ self._account = account
+
+ self.get = async_to_raw_response_wrapper(
+ account.get,
+ )
+
+
+class AccountResourceWithStreamingResponse:
+ def __init__(self, account: AccountResource) -> None:
+ self._account = account
+
+ self.get = to_streamed_response_wrapper(
+ account.get,
+ )
+
+
+class AsyncAccountResourceWithStreamingResponse:
+ def __init__(self, account: AsyncAccountResource) -> None:
+ self._account = account
+
+ self.get = async_to_streamed_response_wrapper(
+ account.get,
+ )
diff --git a/src/cloudflare/resources/dns/usage/usage.py b/src/cloudflare/resources/dns/usage/usage.py
new file mode 100644
index 00000000000..43dd5daf595
--- /dev/null
+++ b/src/cloudflare/resources/dns/usage/usage.py
@@ -0,0 +1,134 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .zone import (
+ ZoneResource,
+ AsyncZoneResource,
+ ZoneResourceWithRawResponse,
+ AsyncZoneResourceWithRawResponse,
+ ZoneResourceWithStreamingResponse,
+ AsyncZoneResourceWithStreamingResponse,
+)
+from .account import (
+ AccountResource,
+ AsyncAccountResource,
+ AccountResourceWithRawResponse,
+ AsyncAccountResourceWithRawResponse,
+ AccountResourceWithStreamingResponse,
+ AsyncAccountResourceWithStreamingResponse,
+)
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+
+__all__ = ["UsageResource", "AsyncUsageResource"]
+
+
+class UsageResource(SyncAPIResource):
+ @cached_property
+ def zone(self) -> ZoneResource:
+ return ZoneResource(self._client)
+
+ @cached_property
+ def account(self) -> AccountResource:
+ return AccountResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> UsageResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#accessing-raw-response-data-eg-headers
+ """
+ return UsageResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> UsageResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#with_streaming_response
+ """
+ return UsageResourceWithStreamingResponse(self)
+
+
+class AsyncUsageResource(AsyncAPIResource):
+ @cached_property
+ def zone(self) -> AsyncZoneResource:
+ return AsyncZoneResource(self._client)
+
+ @cached_property
+ def account(self) -> AsyncAccountResource:
+ return AsyncAccountResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncUsageResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncUsageResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncUsageResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#with_streaming_response
+ """
+ return AsyncUsageResourceWithStreamingResponse(self)
+
+
+class UsageResourceWithRawResponse:
+ def __init__(self, usage: UsageResource) -> None:
+ self._usage = usage
+
+ @cached_property
+ def zone(self) -> ZoneResourceWithRawResponse:
+ return ZoneResourceWithRawResponse(self._usage.zone)
+
+ @cached_property
+ def account(self) -> AccountResourceWithRawResponse:
+ return AccountResourceWithRawResponse(self._usage.account)
+
+
+class AsyncUsageResourceWithRawResponse:
+ def __init__(self, usage: AsyncUsageResource) -> None:
+ self._usage = usage
+
+ @cached_property
+ def zone(self) -> AsyncZoneResourceWithRawResponse:
+ return AsyncZoneResourceWithRawResponse(self._usage.zone)
+
+ @cached_property
+ def account(self) -> AsyncAccountResourceWithRawResponse:
+ return AsyncAccountResourceWithRawResponse(self._usage.account)
+
+
+class UsageResourceWithStreamingResponse:
+ def __init__(self, usage: UsageResource) -> None:
+ self._usage = usage
+
+ @cached_property
+ def zone(self) -> ZoneResourceWithStreamingResponse:
+ return ZoneResourceWithStreamingResponse(self._usage.zone)
+
+ @cached_property
+ def account(self) -> AccountResourceWithStreamingResponse:
+ return AccountResourceWithStreamingResponse(self._usage.account)
+
+
+class AsyncUsageResourceWithStreamingResponse:
+ def __init__(self, usage: AsyncUsageResource) -> None:
+ self._usage = usage
+
+ @cached_property
+ def zone(self) -> AsyncZoneResourceWithStreamingResponse:
+ return AsyncZoneResourceWithStreamingResponse(self._usage.zone)
+
+ @cached_property
+ def account(self) -> AsyncAccountResourceWithStreamingResponse:
+ return AsyncAccountResourceWithStreamingResponse(self._usage.account)
diff --git a/src/cloudflare/resources/dns/usage/zone.py b/src/cloudflare/resources/dns/usage/zone.py
new file mode 100644
index 00000000000..1fd9151910f
--- /dev/null
+++ b/src/cloudflare/resources/dns/usage/zone.py
@@ -0,0 +1,181 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Type, Optional, cast
+
+import httpx
+
+from ...._types import Body, Query, Headers, NotGiven, not_given
+from ...._utils import path_template
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...._wrappers import ResultWrapper
+from ...._base_client import make_request_options
+from ....types.dns.usage.zone_get_response import ZoneGetResponse
+
+__all__ = ["ZoneResource", "AsyncZoneResource"]
+
+
+class ZoneResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> ZoneResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#accessing-raw-response-data-eg-headers
+ """
+ return ZoneResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ZoneResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#with_streaming_response
+ """
+ return ZoneResourceWithStreamingResponse(self)
+
+ def get(
+ self,
+ *,
+ zone_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[ZoneGetResponse]:
+ """
+ Get the current DNS record usage for a zone, including the number of records and
+ the quota limit.
+
+ Args:
+ zone_id: Identifier.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not zone_id:
+ raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}")
+ return self._get(
+ path_template("/zones/{zone_id}/dns_records/usage", zone_id=zone_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[ZoneGetResponse]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[ZoneGetResponse]], ResultWrapper[ZoneGetResponse]),
+ )
+
+
+class AsyncZoneResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncZoneResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncZoneResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncZoneResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#with_streaming_response
+ """
+ return AsyncZoneResourceWithStreamingResponse(self)
+
+ async def get(
+ self,
+ *,
+ zone_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[ZoneGetResponse]:
+ """
+ Get the current DNS record usage for a zone, including the number of records and
+ the quota limit.
+
+ Args:
+ zone_id: Identifier.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not zone_id:
+ raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}")
+ return await self._get(
+ path_template("/zones/{zone_id}/dns_records/usage", zone_id=zone_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[ZoneGetResponse]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[ZoneGetResponse]], ResultWrapper[ZoneGetResponse]),
+ )
+
+
+class ZoneResourceWithRawResponse:
+ def __init__(self, zone: ZoneResource) -> None:
+ self._zone = zone
+
+ self.get = to_raw_response_wrapper(
+ zone.get,
+ )
+
+
+class AsyncZoneResourceWithRawResponse:
+ def __init__(self, zone: AsyncZoneResource) -> None:
+ self._zone = zone
+
+ self.get = async_to_raw_response_wrapper(
+ zone.get,
+ )
+
+
+class ZoneResourceWithStreamingResponse:
+ def __init__(self, zone: ZoneResource) -> None:
+ self._zone = zone
+
+ self.get = to_streamed_response_wrapper(
+ zone.get,
+ )
+
+
+class AsyncZoneResourceWithStreamingResponse:
+ def __init__(self, zone: AsyncZoneResource) -> None:
+ self._zone = zone
+
+ self.get = async_to_streamed_response_wrapper(
+ zone.get,
+ )
diff --git a/src/cloudflare/types/dns/usage/__init__.py b/src/cloudflare/types/dns/usage/__init__.py
index f8ee8b14b1c..4e413a5b08f 100644
--- a/src/cloudflare/types/dns/usage/__init__.py
+++ b/src/cloudflare/types/dns/usage/__init__.py
@@ -1,3 +1,6 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from __future__ import annotations
+
+from .zone_get_response import ZoneGetResponse as ZoneGetResponse
+from .account_get_response import AccountGetResponse as AccountGetResponse
diff --git a/src/cloudflare/types/dns/usage/account_get_response.py b/src/cloudflare/types/dns/usage/account_get_response.py
new file mode 100644
index 00000000000..86a020719ac
--- /dev/null
+++ b/src/cloudflare/types/dns/usage/account_get_response.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ...._models import BaseModel
+
+__all__ = ["AccountGetResponse"]
+
+
+class AccountGetResponse(BaseModel):
+ record_quota: Optional[int] = None
+ """Maximum number of DNS records allowed across all public zones in the account.
+
+ Null if using zone-level quota.
+ """
+
+ record_usage: int
+ """Current number of DNS records across all public zones in the account."""
+
+ internal_record_quota: Optional[int] = None
+ """Maximum number of DNS records allowed across all internal zones in the account.
+
+ Only present if internal DNS is enabled.
+ """
+
+ internal_record_usage: Optional[int] = None
+ """Current number of DNS records across all internal zones in the account.
+
+ Only present if internal DNS is enabled.
+ """
diff --git a/src/cloudflare/types/dns/usage/zone_get_response.py b/src/cloudflare/types/dns/usage/zone_get_response.py
new file mode 100644
index 00000000000..3c939e08972
--- /dev/null
+++ b/src/cloudflare/types/dns/usage/zone_get_response.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ...._models import BaseModel
+
+__all__ = ["ZoneGetResponse"]
+
+
+class ZoneGetResponse(BaseModel):
+ record_quota: Optional[int] = None
+ """Maximum number of DNS records allowed for the zone.
+
+ Null if using account-level quota.
+ """
+
+ record_usage: int
+ """Current number of DNS records in the zone."""
diff --git a/tests/api_resources/dns/usage/__init__.py b/tests/api_resources/dns/usage/__init__.py
new file mode 100644
index 00000000000..fd8019a9a1a
--- /dev/null
+++ b/tests/api_resources/dns/usage/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/dns/usage/test_account.py b/tests/api_resources/dns/usage/test_account.py
new file mode 100644
index 00000000000..03d86dca84a
--- /dev/null
+++ b/tests/api_resources/dns/usage/test_account.py
@@ -0,0 +1,100 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, Optional, cast
+
+import pytest
+
+from cloudflare import Cloudflare, AsyncCloudflare
+from tests.utils import assert_matches_type
+from cloudflare.types.dns.usage import AccountGetResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestAccount:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_get(self, client: Cloudflare) -> None:
+ account = client.dns.usage.account.get(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+ assert_matches_type(Optional[AccountGetResponse], account, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Cloudflare) -> None:
+ response = client.dns.usage.account.with_raw_response.get(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ account = response.parse()
+ assert_matches_type(Optional[AccountGetResponse], account, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Cloudflare) -> None:
+ with client.dns.usage.account.with_streaming_response.get(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ account = response.parse()
+ assert_matches_type(Optional[AccountGetResponse], account, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Cloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ client.dns.usage.account.with_raw_response.get(
+ account_id="",
+ )
+
+
+class TestAsyncAccount:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncCloudflare) -> None:
+ account = await async_client.dns.usage.account.get(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+ assert_matches_type(Optional[AccountGetResponse], account, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncCloudflare) -> None:
+ response = await async_client.dns.usage.account.with_raw_response.get(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ account = await response.parse()
+ assert_matches_type(Optional[AccountGetResponse], account, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncCloudflare) -> None:
+ async with async_client.dns.usage.account.with_streaming_response.get(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ account = await response.parse()
+ assert_matches_type(Optional[AccountGetResponse], account, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncCloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ await async_client.dns.usage.account.with_raw_response.get(
+ account_id="",
+ )
diff --git a/tests/api_resources/dns/usage/test_zone.py b/tests/api_resources/dns/usage/test_zone.py
new file mode 100644
index 00000000000..be5c61715f1
--- /dev/null
+++ b/tests/api_resources/dns/usage/test_zone.py
@@ -0,0 +1,100 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, Optional, cast
+
+import pytest
+
+from cloudflare import Cloudflare, AsyncCloudflare
+from tests.utils import assert_matches_type
+from cloudflare.types.dns.usage import ZoneGetResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestZone:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_get(self, client: Cloudflare) -> None:
+ zone = client.dns.usage.zone.get(
+ zone_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+ assert_matches_type(Optional[ZoneGetResponse], zone, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Cloudflare) -> None:
+ response = client.dns.usage.zone.with_raw_response.get(
+ zone_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ zone = response.parse()
+ assert_matches_type(Optional[ZoneGetResponse], zone, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Cloudflare) -> None:
+ with client.dns.usage.zone.with_streaming_response.get(
+ zone_id="023e105f4ecef8ad9ca31a8372d0c353",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ zone = response.parse()
+ assert_matches_type(Optional[ZoneGetResponse], zone, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Cloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `zone_id` but received ''"):
+ client.dns.usage.zone.with_raw_response.get(
+ zone_id="",
+ )
+
+
+class TestAsyncZone:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncCloudflare) -> None:
+ zone = await async_client.dns.usage.zone.get(
+ zone_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+ assert_matches_type(Optional[ZoneGetResponse], zone, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncCloudflare) -> None:
+ response = await async_client.dns.usage.zone.with_raw_response.get(
+ zone_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ zone = await response.parse()
+ assert_matches_type(Optional[ZoneGetResponse], zone, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncCloudflare) -> None:
+ async with async_client.dns.usage.zone.with_streaming_response.get(
+ zone_id="023e105f4ecef8ad9ca31a8372d0c353",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ zone = await response.parse()
+ assert_matches_type(Optional[ZoneGetResponse], zone, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncCloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `zone_id` but received ''"):
+ await async_client.dns.usage.zone.with_raw_response.get(
+ zone_id="",
+ )
From f88efedc777742aed07a10f516bf38f856d16462 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Jun 2026 15:50:11 +0000
Subject: [PATCH 11/18] chore(api): update composite API spec
---
.stats.yml | 4 +--
.../resources/workflows/workflows.py | 6 +++-
.../access/ai_controls/mcp/servers.py | 32 +++++++++++++++++++
.../types/workflows/instance_bulk_response.py | 3 ++
.../workflows/instance_create_response.py | 3 ++
.../types/workflows/instance_get_response.py | 9 ++++++
.../types/workflows/instance_list_response.py | 2 ++
.../types/workflows/workflow_get_response.py | 12 +++++--
.../types/workflows/workflow_list_response.py | 12 +++++--
.../types/workflows/workflow_update_params.py | 9 +++++-
.../ai_controls/mcp/portal_create_response.py | 6 ++--
.../ai_controls/mcp/portal_list_response.py | 6 ++--
.../ai_controls/mcp/portal_read_response.py | 6 ++--
.../ai_controls/mcp/portal_update_response.py | 6 ++--
.../ai_controls/mcp/server_create_params.py | 9 ++++++
.../ai_controls/mcp/server_create_response.py | 6 ++--
.../ai_controls/mcp/server_delete_response.py | 6 ++--
.../ai_controls/mcp/server_list_response.py | 6 ++--
.../ai_controls/mcp/server_read_response.py | 6 ++--
.../ai_controls/mcp/server_update_params.py | 9 ++++++
.../ai_controls/mcp/server_update_response.py | 6 ++--
tests/api_resources/test_workflows.py | 2 ++
.../access/ai_controls/mcp/test_servers.py | 4 +++
23 files changed, 135 insertions(+), 35 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index c11620ea6f2..bcfad689b07 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 2319
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-95e73b4de689baa5f103f6b57f8b0c16e7a2380a5a707cbf8c35c33537adba54.yml
-openapi_spec_hash: 9f044e790240c1256984f011c9b6539b
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-13182d492c29e38bbe0bbe054e2164a7fafd14f41bbf6f80edf28d4b1d1eead6.yml
+openapi_spec_hash: 3005ae41716ac63d8ce8507844e6c71c
config_hash: 3d78de7487c424794f10d37798c7cf95
diff --git a/src/cloudflare/resources/workflows/workflows.py b/src/cloudflare/resources/workflows/workflows.py
index 803e81f301d..e8a57121959 100644
--- a/src/cloudflare/resources/workflows/workflows.py
+++ b/src/cloudflare/resources/workflows/workflows.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Type, cast
+from typing import Type, Iterable, cast
import httpx
@@ -80,6 +80,7 @@ def update(
class_name: str,
script_name: str,
limits: workflow_update_params.Limits | Omit = omit,
+ schedules: Iterable[workflow_update_params.Schedule] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -112,6 +113,7 @@ def update(
"class_name": class_name,
"script_name": script_name,
"limits": limits,
+ "schedules": schedules,
},
workflow_update_params.WorkflowUpdateParams,
),
@@ -298,6 +300,7 @@ async def update(
class_name: str,
script_name: str,
limits: workflow_update_params.Limits | Omit = omit,
+ schedules: Iterable[workflow_update_params.Schedule] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -330,6 +333,7 @@ async def update(
"class_name": class_name,
"script_name": script_name,
"limits": limits,
+ "schedules": schedules,
},
workflow_update_params.WorkflowUpdateParams,
),
diff --git a/src/cloudflare/resources/zero_trust/access/ai_controls/mcp/servers.py b/src/cloudflare/resources/zero_trust/access/ai_controls/mcp/servers.py
index b855f84cbdc..3c874c8e772 100644
--- a/src/cloudflare/resources/zero_trust/access/ai_controls/mcp/servers.py
+++ b/src/cloudflare/resources/zero_trust/access/ai_controls/mcp/servers.py
@@ -61,6 +61,7 @@ def create(
name: str,
auth_credentials: str | Omit = omit,
description: Optional[str] | Omit = omit,
+ is_shared_oauth_callback_enabled: bool | Omit = omit,
updated_prompts: Iterable[server_create_params.UpdatedPrompt] | Omit = omit,
updated_tools: Iterable[server_create_params.UpdatedTool] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -76,6 +77,12 @@ def create(
Args:
id: server id
+ is_shared_oauth_callback_enabled: When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
+ endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -96,6 +103,7 @@ def create(
"name": name,
"auth_credentials": auth_credentials,
"description": description,
+ "is_shared_oauth_callback_enabled": is_shared_oauth_callback_enabled,
"updated_prompts": updated_prompts,
"updated_tools": updated_tools,
},
@@ -118,6 +126,7 @@ def update(
account_id: str,
auth_credentials: str | Omit = omit,
description: Optional[str] | Omit = omit,
+ is_shared_oauth_callback_enabled: bool | Omit = omit,
name: str | Omit = omit,
updated_prompts: Iterable[server_update_params.UpdatedPrompt] | Omit = omit,
updated_tools: Iterable[server_update_params.UpdatedTool] | Omit = omit,
@@ -134,6 +143,12 @@ def update(
Args:
id: server id
+ is_shared_oauth_callback_enabled: When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
+ endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -152,6 +167,7 @@ def update(
{
"auth_credentials": auth_credentials,
"description": description,
+ "is_shared_oauth_callback_enabled": is_shared_oauth_callback_enabled,
"name": name,
"updated_prompts": updated_prompts,
"updated_tools": updated_tools,
@@ -378,6 +394,7 @@ async def create(
name: str,
auth_credentials: str | Omit = omit,
description: Optional[str] | Omit = omit,
+ is_shared_oauth_callback_enabled: bool | Omit = omit,
updated_prompts: Iterable[server_create_params.UpdatedPrompt] | Omit = omit,
updated_tools: Iterable[server_create_params.UpdatedTool] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -393,6 +410,12 @@ async def create(
Args:
id: server id
+ is_shared_oauth_callback_enabled: When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
+ endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -413,6 +436,7 @@ async def create(
"name": name,
"auth_credentials": auth_credentials,
"description": description,
+ "is_shared_oauth_callback_enabled": is_shared_oauth_callback_enabled,
"updated_prompts": updated_prompts,
"updated_tools": updated_tools,
},
@@ -435,6 +459,7 @@ async def update(
account_id: str,
auth_credentials: str | Omit = omit,
description: Optional[str] | Omit = omit,
+ is_shared_oauth_callback_enabled: bool | Omit = omit,
name: str | Omit = omit,
updated_prompts: Iterable[server_update_params.UpdatedPrompt] | Omit = omit,
updated_tools: Iterable[server_update_params.UpdatedTool] | Omit = omit,
@@ -451,6 +476,12 @@ async def update(
Args:
id: server id
+ is_shared_oauth_callback_enabled: When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
+ endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -469,6 +500,7 @@ async def update(
{
"auth_credentials": auth_credentials,
"description": description,
+ "is_shared_oauth_callback_enabled": is_shared_oauth_callback_enabled,
"name": name,
"updated_prompts": updated_prompts,
"updated_tools": updated_tools,
diff --git a/src/cloudflare/types/workflows/instance_bulk_response.py b/src/cloudflare/types/workflows/instance_bulk_response.py
index d941e320187..18348034307 100644
--- a/src/cloudflare/types/workflows/instance_bulk_response.py
+++ b/src/cloudflare/types/workflows/instance_bulk_response.py
@@ -1,5 +1,6 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from typing import Optional
from typing_extensions import Literal
from ..._models import BaseModel
@@ -17,3 +18,5 @@ class InstanceBulkResponse(BaseModel):
version_id: str
workflow_id: str
+
+ trigger_source: Optional[Literal["unknown", "api", "binding", "event", "cron"]] = None
diff --git a/src/cloudflare/types/workflows/instance_create_response.py b/src/cloudflare/types/workflows/instance_create_response.py
index 76df532ca4c..12496c29da1 100644
--- a/src/cloudflare/types/workflows/instance_create_response.py
+++ b/src/cloudflare/types/workflows/instance_create_response.py
@@ -1,5 +1,6 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from typing import Optional
from typing_extensions import Literal
from ..._models import BaseModel
@@ -17,3 +18,5 @@ class InstanceCreateResponse(BaseModel):
version_id: str
workflow_id: str
+
+ trigger_source: Optional[Literal["unknown", "api", "binding", "event", "cron"]] = None
diff --git a/src/cloudflare/types/workflows/instance_get_response.py b/src/cloudflare/types/workflows/instance_get_response.py
index 90f743706f3..f9e0ddad80f 100644
--- a/src/cloudflare/types/workflows/instance_get_response.py
+++ b/src/cloudflare/types/workflows/instance_get_response.py
@@ -26,6 +26,7 @@
"StepUnionMember3",
"StepUnionMember3Error",
"Trigger",
+ "Schedule",
]
@@ -162,6 +163,12 @@ class Trigger(BaseModel):
source: Literal["unknown", "api", "binding", "event", "cron"]
+class Schedule(BaseModel):
+ cron: str
+
+ scheduled_time: float = FieldInfo(alias="scheduledTime")
+
+
class InstanceGetResponse(BaseModel):
end: Optional[datetime] = None
@@ -190,3 +197,5 @@ class InstanceGetResponse(BaseModel):
trigger: Trigger
version_id: str = FieldInfo(alias="versionId")
+
+ schedule: Optional[Schedule] = None
diff --git a/src/cloudflare/types/workflows/instance_list_response.py b/src/cloudflare/types/workflows/instance_list_response.py
index 7a266896551..ea6f15bb284 100644
--- a/src/cloudflare/types/workflows/instance_list_response.py
+++ b/src/cloudflare/types/workflows/instance_list_response.py
@@ -27,3 +27,5 @@ class InstanceListResponse(BaseModel):
version_id: str
workflow_id: str
+
+ trigger_source: Optional[Literal["unknown", "api", "binding", "event", "cron"]] = None
diff --git a/src/cloudflare/types/workflows/workflow_get_response.py b/src/cloudflare/types/workflows/workflow_get_response.py
index 7698baaaa17..0553291d9f0 100644
--- a/src/cloudflare/types/workflows/workflow_get_response.py
+++ b/src/cloudflare/types/workflows/workflow_get_response.py
@@ -1,13 +1,13 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
+from typing import List, Optional
from datetime import datetime
from pydantic import Field as FieldInfo
from ..._models import BaseModel
-__all__ = ["WorkflowGetResponse", "Instances"]
+__all__ = ["WorkflowGetResponse", "Instances", "Schedule"]
class Instances(BaseModel):
@@ -30,6 +30,12 @@ class Instances(BaseModel):
waiting_for_pause: Optional[float] = FieldInfo(alias="waitingForPause", default=None)
+class Schedule(BaseModel):
+ cron: str
+
+ next_instance: str
+
+
class WorkflowGetResponse(BaseModel):
id: str
@@ -46,3 +52,5 @@ class WorkflowGetResponse(BaseModel):
script_name: str
triggered_on: Optional[datetime] = None
+
+ schedules: Optional[List[Schedule]] = None
diff --git a/src/cloudflare/types/workflows/workflow_list_response.py b/src/cloudflare/types/workflows/workflow_list_response.py
index bde9d7dd38c..a05251d55ce 100644
--- a/src/cloudflare/types/workflows/workflow_list_response.py
+++ b/src/cloudflare/types/workflows/workflow_list_response.py
@@ -1,13 +1,13 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
+from typing import List, Optional
from datetime import datetime
from pydantic import Field as FieldInfo
from ..._models import BaseModel
-__all__ = ["WorkflowListResponse", "Instances"]
+__all__ = ["WorkflowListResponse", "Instances", "Schedule"]
class Instances(BaseModel):
@@ -30,6 +30,12 @@ class Instances(BaseModel):
waiting_for_pause: Optional[float] = FieldInfo(alias="waitingForPause", default=None)
+class Schedule(BaseModel):
+ cron: str
+
+ next_instance: str
+
+
class WorkflowListResponse(BaseModel):
id: str
@@ -46,3 +52,5 @@ class WorkflowListResponse(BaseModel):
script_name: str
triggered_on: Optional[datetime] = None
+
+ schedules: Optional[List[Schedule]] = None
diff --git a/src/cloudflare/types/workflows/workflow_update_params.py b/src/cloudflare/types/workflows/workflow_update_params.py
index 2ff781a484d..045b90afdf6 100644
--- a/src/cloudflare/types/workflows/workflow_update_params.py
+++ b/src/cloudflare/types/workflows/workflow_update_params.py
@@ -2,9 +2,10 @@
from __future__ import annotations
+from typing import Iterable
from typing_extensions import Required, TypedDict
-__all__ = ["WorkflowUpdateParams", "Limits"]
+__all__ = ["WorkflowUpdateParams", "Limits", "Schedule"]
class WorkflowUpdateParams(TypedDict, total=False):
@@ -16,6 +17,12 @@ class WorkflowUpdateParams(TypedDict, total=False):
limits: Limits
+ schedules: Iterable[Schedule]
+
class Limits(TypedDict, total=False):
steps: int
+
+
+class Schedule(TypedDict, total=False):
+ cron: Required[str]
diff --git a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_create_response.py b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_create_response.py
index 7dc526be769..61c6e6f7223 100644
--- a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_create_response.py
+++ b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_create_response.py
@@ -84,9 +84,9 @@ class Server(BaseModel):
"""
When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
- customer portal hostname. Operators manage this internal rollout flag through
- admin endpoints. Effective behavior is gated by the gateway worker's per-env
- rollout mode KV key.
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
"""
last_successful_sync: Optional[datetime] = None
diff --git a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_list_response.py b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_list_response.py
index 05017bfb676..3008b57f175 100644
--- a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_list_response.py
+++ b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_list_response.py
@@ -84,9 +84,9 @@ class Server(BaseModel):
"""
When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
- customer portal hostname. Operators manage this internal rollout flag through
- admin endpoints. Effective behavior is gated by the gateway worker's per-env
- rollout mode KV key.
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
"""
last_successful_sync: Optional[datetime] = None
diff --git a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_read_response.py b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_read_response.py
index 1b65b772635..07ed7ca9071 100644
--- a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_read_response.py
+++ b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_read_response.py
@@ -84,9 +84,9 @@ class Server(BaseModel):
"""
When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
- customer portal hostname. Operators manage this internal rollout flag through
- admin endpoints. Effective behavior is gated by the gateway worker's per-env
- rollout mode KV key.
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
"""
last_successful_sync: Optional[datetime] = None
diff --git a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_update_response.py b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_update_response.py
index 42790e98208..a29b140f119 100644
--- a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_update_response.py
+++ b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/portal_update_response.py
@@ -84,9 +84,9 @@ class Server(BaseModel):
"""
When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
- customer portal hostname. Operators manage this internal rollout flag through
- admin endpoints. Effective behavior is gated by the gateway worker's per-env
- rollout mode KV key.
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
"""
last_successful_sync: Optional[datetime] = None
diff --git a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_create_params.py b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_create_params.py
index 57c26163547..598d4762a3f 100644
--- a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_create_params.py
+++ b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_create_params.py
@@ -24,6 +24,15 @@ class ServerCreateParams(TypedDict, total=False):
description: Optional[str]
+ is_shared_oauth_callback_enabled: bool
+ """
+ When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
+ endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
+ """
+
updated_prompts: Iterable[UpdatedPrompt]
updated_tools: Iterable[UpdatedTool]
diff --git a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_create_response.py b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_create_response.py
index df613df40b6..815b4463833 100644
--- a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_create_response.py
+++ b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_create_response.py
@@ -74,9 +74,9 @@ class ServerCreateResponse(BaseModel):
"""
When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
- customer portal hostname. Operators manage this internal rollout flag through
- admin endpoints. Effective behavior is gated by the gateway worker's per-env
- rollout mode KV key.
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
"""
last_successful_sync: Optional[datetime] = None
diff --git a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_delete_response.py b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_delete_response.py
index e26d32a3331..458ed1089ac 100644
--- a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_delete_response.py
+++ b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_delete_response.py
@@ -74,9 +74,9 @@ class ServerDeleteResponse(BaseModel):
"""
When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
- customer portal hostname. Operators manage this internal rollout flag through
- admin endpoints. Effective behavior is gated by the gateway worker's per-env
- rollout mode KV key.
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
"""
last_successful_sync: Optional[datetime] = None
diff --git a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_list_response.py b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_list_response.py
index fe5dffa46b6..7e12dd45fb8 100644
--- a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_list_response.py
+++ b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_list_response.py
@@ -74,9 +74,9 @@ class ServerListResponse(BaseModel):
"""
When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
- customer portal hostname. Operators manage this internal rollout flag through
- admin endpoints. Effective behavior is gated by the gateway worker's per-env
- rollout mode KV key.
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
"""
last_successful_sync: Optional[datetime] = None
diff --git a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_read_response.py b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_read_response.py
index af85d6b8528..259bcd23a18 100644
--- a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_read_response.py
+++ b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_read_response.py
@@ -74,9 +74,9 @@ class ServerReadResponse(BaseModel):
"""
When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
- customer portal hostname. Operators manage this internal rollout flag through
- admin endpoints. Effective behavior is gated by the gateway worker's per-env
- rollout mode KV key.
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
"""
last_successful_sync: Optional[datetime] = None
diff --git a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_update_params.py b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_update_params.py
index 38cf578f571..394c9e5a3f4 100644
--- a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_update_params.py
+++ b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_update_params.py
@@ -15,6 +15,15 @@ class ServerUpdateParams(TypedDict, total=False):
description: Optional[str]
+ is_shared_oauth_callback_enabled: bool
+ """
+ When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
+ endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
+ """
+
name: str
updated_prompts: Iterable[UpdatedPrompt]
diff --git a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_update_response.py b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_update_response.py
index 19cf816346a..af37f6b9644 100644
--- a/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_update_response.py
+++ b/src/cloudflare/types/zero_trust/access/ai_controls/mcp/server_update_response.py
@@ -74,9 +74,9 @@ class ServerUpdateResponse(BaseModel):
"""
When true, the gateway worker uses the shared Cloudflare-owned OAuth callback
endpoint as the redirect_uri for upstream on-behalf OAuth, instead of the
- customer portal hostname. Operators manage this internal rollout flag through
- admin endpoints. Effective behavior is gated by the gateway worker's per-env
- rollout mode KV key.
+ customer portal hostname. New public server creates default to true; existing
+ servers default to false from migration until explicitly updated. Effective
+ behavior is gated by the gateway worker's per-env rollout mode KV key.
"""
last_successful_sync: Optional[datetime] = None
diff --git a/tests/api_resources/test_workflows.py b/tests/api_resources/test_workflows.py
index 50b603500c7..d8dc7b04888 100644
--- a/tests/api_resources/test_workflows.py
+++ b/tests/api_resources/test_workflows.py
@@ -41,6 +41,7 @@ def test_method_update_with_all_params(self, client: Cloudflare) -> None:
class_name="x",
script_name="x",
limits={"steps": 1},
+ schedules=[{"cron": "x"}],
)
assert_matches_type(WorkflowUpdateResponse, workflow, path=["response"])
@@ -260,6 +261,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCloudflare
class_name="x",
script_name="x",
limits={"steps": 1},
+ schedules=[{"cron": "x"}],
)
assert_matches_type(WorkflowUpdateResponse, workflow, path=["response"])
diff --git a/tests/api_resources/zero_trust/access/ai_controls/mcp/test_servers.py b/tests/api_resources/zero_trust/access/ai_controls/mcp/test_servers.py
index 730d3ecb17d..33145a04d0d 100644
--- a/tests/api_resources/zero_trust/access/ai_controls/mcp/test_servers.py
+++ b/tests/api_resources/zero_trust/access/ai_controls/mcp/test_servers.py
@@ -46,6 +46,7 @@ def test_method_create_with_all_params(self, client: Cloudflare) -> None:
name="My MCP Server",
auth_credentials="auth_credentials",
description="This is one remote mcp server",
+ is_shared_oauth_callback_enabled=True,
updated_prompts=[
{
"name": "name",
@@ -123,6 +124,7 @@ def test_method_update_with_all_params(self, client: Cloudflare) -> None:
account_id="a86a8f5c339544d7bdc89926de14fb8c",
auth_credentials="auth_credentials",
description="This is one remote mcp server",
+ is_shared_oauth_callback_enabled=True,
name="My MCP Server",
updated_prompts=[
{
@@ -402,6 +404,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCloudflare
name="My MCP Server",
auth_credentials="auth_credentials",
description="This is one remote mcp server",
+ is_shared_oauth_callback_enabled=True,
updated_prompts=[
{
"name": "name",
@@ -479,6 +482,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCloudflare
account_id="a86a8f5c339544d7bdc89926de14fb8c",
auth_credentials="auth_credentials",
description="This is one remote mcp server",
+ is_shared_oauth_callback_enabled=True,
name="My MCP Server",
updated_prompts=[
{
From 4b724b0b1464369c82896f60406ac63269fdbc22 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Jun 2026 17:36:31 +0000
Subject: [PATCH 12/18] feat: feat(access): add idp_federation_grants resource
* feat(access): add idp_federation_grants resource
Add list/create/get/delete endpoints for Access IdP federation grants
under zero_trust.access.
Closes AUTH-8707
---
.stats.yml | 6 +-
.../resources/zero_trust/access/__init__.py | 14 +
.../resources/zero_trust/access/access.py | 32 ++
.../access/idp_federation_grants.py | 520 ++++++++++++++++++
src/cloudflare/resources/zero_trust/api.md | 19 +
.../types/zero_trust/access/__init__.py | 4 +
.../zero_trust/access/idp_federation_grant.py | 18 +
.../idp_federation_grant_create_params.py | 19 +
.../idp_federation_grant_delete_response.py | 12 +
.../idp_federation_grant_list_response.py | 10 +
.../access/test_idp_federation_grants.py | 380 +++++++++++++
11 files changed, 1031 insertions(+), 3 deletions(-)
create mode 100644 src/cloudflare/resources/zero_trust/access/idp_federation_grants.py
create mode 100644 src/cloudflare/types/zero_trust/access/idp_federation_grant.py
create mode 100644 src/cloudflare/types/zero_trust/access/idp_federation_grant_create_params.py
create mode 100644 src/cloudflare/types/zero_trust/access/idp_federation_grant_delete_response.py
create mode 100644 src/cloudflare/types/zero_trust/access/idp_federation_grant_list_response.py
create mode 100644 tests/api_resources/zero_trust/access/test_idp_federation_grants.py
diff --git a/.stats.yml b/.stats.yml
index bcfad689b07..99ffe46629d 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 2319
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-13182d492c29e38bbe0bbe054e2164a7fafd14f41bbf6f80edf28d4b1d1eead6.yml
+configured_endpoints: 2323
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-59bd5e43dfef7cf91cec9008063c948951ab4a8b7614c09b916c226bc737c532.yml
openapi_spec_hash: 3005ae41716ac63d8ce8507844e6c71c
-config_hash: 3d78de7487c424794f10d37798c7cf95
+config_hash: d356305f9e4bef414359ff0156867a23
diff --git a/src/cloudflare/resources/zero_trust/access/__init__.py b/src/cloudflare/resources/zero_trust/access/__init__.py
index ac8d0a9fa77..f8e1684a27b 100644
--- a/src/cloudflare/resources/zero_trust/access/__init__.py
+++ b/src/cloudflare/resources/zero_trust/access/__init__.py
@@ -128,6 +128,14 @@
SAMLCertificatesResourceWithStreamingResponse,
AsyncSAMLCertificatesResourceWithStreamingResponse,
)
+from .idp_federation_grants import (
+ IdPFederationGrantsResource,
+ AsyncIdPFederationGrantsResource,
+ IdPFederationGrantsResourceWithRawResponse,
+ AsyncIdPFederationGrantsResourceWithRawResponse,
+ IdPFederationGrantsResourceWithStreamingResponse,
+ AsyncIdPFederationGrantsResourceWithStreamingResponse,
+)
__all__ = [
"AIControlsResource",
@@ -142,6 +150,12 @@
"AsyncGatewayCAResourceWithRawResponse",
"GatewayCAResourceWithStreamingResponse",
"AsyncGatewayCAResourceWithStreamingResponse",
+ "IdPFederationGrantsResource",
+ "AsyncIdPFederationGrantsResource",
+ "IdPFederationGrantsResourceWithRawResponse",
+ "AsyncIdPFederationGrantsResourceWithRawResponse",
+ "IdPFederationGrantsResourceWithStreamingResponse",
+ "AsyncIdPFederationGrantsResourceWithStreamingResponse",
"SAMLCertificatesResource",
"AsyncSAMLCertificatesResource",
"SAMLCertificatesResourceWithRawResponse",
diff --git a/src/cloudflare/resources/zero_trust/access/access.py b/src/cloudflare/resources/zero_trust/access/access.py
index f9f363b3313..9efd000a88a 100644
--- a/src/cloudflare/resources/zero_trust/access/access.py
+++ b/src/cloudflare/resources/zero_trust/access/access.py
@@ -92,6 +92,14 @@
SAMLCertificatesResourceWithStreamingResponse,
AsyncSAMLCertificatesResourceWithStreamingResponse,
)
+from .idp_federation_grants import (
+ IdPFederationGrantsResource,
+ AsyncIdPFederationGrantsResource,
+ IdPFederationGrantsResourceWithRawResponse,
+ AsyncIdPFederationGrantsResourceWithRawResponse,
+ IdPFederationGrantsResourceWithStreamingResponse,
+ AsyncIdPFederationGrantsResourceWithStreamingResponse,
+)
from .ai_controls.ai_controls import (
AIControlsResource,
AsyncAIControlsResource,
@@ -137,6 +145,10 @@ def ai_controls(self) -> AIControlsResource:
def gateway_ca(self) -> GatewayCAResource:
return GatewayCAResource(self._client)
+ @cached_property
+ def idp_federation_grants(self) -> IdPFederationGrantsResource:
+ return IdPFederationGrantsResource(self._client)
+
@cached_property
def saml_certificates(self) -> SAMLCertificatesResource:
return SAMLCertificatesResource(self._client)
@@ -218,6 +230,10 @@ def ai_controls(self) -> AsyncAIControlsResource:
def gateway_ca(self) -> AsyncGatewayCAResource:
return AsyncGatewayCAResource(self._client)
+ @cached_property
+ def idp_federation_grants(self) -> AsyncIdPFederationGrantsResource:
+ return AsyncIdPFederationGrantsResource(self._client)
+
@cached_property
def saml_certificates(self) -> AsyncSAMLCertificatesResource:
return AsyncSAMLCertificatesResource(self._client)
@@ -302,6 +318,10 @@ def ai_controls(self) -> AIControlsResourceWithRawResponse:
def gateway_ca(self) -> GatewayCAResourceWithRawResponse:
return GatewayCAResourceWithRawResponse(self._access.gateway_ca)
+ @cached_property
+ def idp_federation_grants(self) -> IdPFederationGrantsResourceWithRawResponse:
+ return IdPFederationGrantsResourceWithRawResponse(self._access.idp_federation_grants)
+
@cached_property
def saml_certificates(self) -> SAMLCertificatesResourceWithRawResponse:
return SAMLCertificatesResourceWithRawResponse(self._access.saml_certificates)
@@ -367,6 +387,10 @@ def ai_controls(self) -> AsyncAIControlsResourceWithRawResponse:
def gateway_ca(self) -> AsyncGatewayCAResourceWithRawResponse:
return AsyncGatewayCAResourceWithRawResponse(self._access.gateway_ca)
+ @cached_property
+ def idp_federation_grants(self) -> AsyncIdPFederationGrantsResourceWithRawResponse:
+ return AsyncIdPFederationGrantsResourceWithRawResponse(self._access.idp_federation_grants)
+
@cached_property
def saml_certificates(self) -> AsyncSAMLCertificatesResourceWithRawResponse:
return AsyncSAMLCertificatesResourceWithRawResponse(self._access.saml_certificates)
@@ -432,6 +456,10 @@ def ai_controls(self) -> AIControlsResourceWithStreamingResponse:
def gateway_ca(self) -> GatewayCAResourceWithStreamingResponse:
return GatewayCAResourceWithStreamingResponse(self._access.gateway_ca)
+ @cached_property
+ def idp_federation_grants(self) -> IdPFederationGrantsResourceWithStreamingResponse:
+ return IdPFederationGrantsResourceWithStreamingResponse(self._access.idp_federation_grants)
+
@cached_property
def saml_certificates(self) -> SAMLCertificatesResourceWithStreamingResponse:
return SAMLCertificatesResourceWithStreamingResponse(self._access.saml_certificates)
@@ -497,6 +525,10 @@ def ai_controls(self) -> AsyncAIControlsResourceWithStreamingResponse:
def gateway_ca(self) -> AsyncGatewayCAResourceWithStreamingResponse:
return AsyncGatewayCAResourceWithStreamingResponse(self._access.gateway_ca)
+ @cached_property
+ def idp_federation_grants(self) -> AsyncIdPFederationGrantsResourceWithStreamingResponse:
+ return AsyncIdPFederationGrantsResourceWithStreamingResponse(self._access.idp_federation_grants)
+
@cached_property
def saml_certificates(self) -> AsyncSAMLCertificatesResourceWithStreamingResponse:
return AsyncSAMLCertificatesResourceWithStreamingResponse(self._access.saml_certificates)
diff --git a/src/cloudflare/resources/zero_trust/access/idp_federation_grants.py b/src/cloudflare/resources/zero_trust/access/idp_federation_grants.py
new file mode 100644
index 00000000000..89da448503a
--- /dev/null
+++ b/src/cloudflare/resources/zero_trust/access/idp_federation_grants.py
@@ -0,0 +1,520 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Type, Optional, cast
+
+import httpx
+
+from ...._types import Body, Query, Headers, NotGiven, not_given
+from ...._utils import path_template, maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...._wrappers import ResultWrapper
+from ...._base_client import make_request_options
+from ....types.zero_trust.access import idp_federation_grant_create_params
+from ....types.zero_trust.access.idp_federation_grant import IdPFederationGrant
+from ....types.zero_trust.access.idp_federation_grant_list_response import IdPFederationGrantListResponse
+from ....types.zero_trust.access.idp_federation_grant_delete_response import IdPFederationGrantDeleteResponse
+
+__all__ = ["IdPFederationGrantsResource", "AsyncIdPFederationGrantsResource"]
+
+
+class IdPFederationGrantsResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> IdPFederationGrantsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#accessing-raw-response-data-eg-headers
+ """
+ return IdPFederationGrantsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> IdPFederationGrantsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#with_streaming_response
+ """
+ return IdPFederationGrantsResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ account_id: str,
+ idp_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[IdPFederationGrant]:
+ """
+ Creates an IdP federation grant for the specified identity provider, making it
+ available for federation to other accounts in the same Cloudflare organization.
+
+ The account must belong to a Cloudflare organization. One-time pin and
+ Cloudflare-managed identity providers cannot be federated. An identity provider
+ may only have one active grant at a time.
+
+ Args:
+ account_id: Identifier.
+
+ idp_id: UID of the identity provider to federate. Must be an existing identity provider
+ in this account. One-time pin and Cloudflare-managed identity providers cannot
+ be federated.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ return self._post(
+ path_template("/accounts/{account_id}/access/idp_federation_grants", account_id=account_id),
+ body=maybe_transform({"idp_id": idp_id}, idp_federation_grant_create_params.IdPFederationGrantCreateParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[IdPFederationGrant]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[IdPFederationGrant]], ResultWrapper[IdPFederationGrant]),
+ )
+
+ def list(
+ self,
+ *,
+ account_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[IdPFederationGrantListResponse]:
+ """
+ Lists the IdP federation grants owned by the account.
+
+ Args:
+ account_id: Identifier.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ return self._get(
+ path_template("/accounts/{account_id}/access/idp_federation_grants", account_id=account_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[IdPFederationGrantListResponse]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[IdPFederationGrantListResponse]], ResultWrapper[IdPFederationGrantListResponse]),
+ )
+
+ def delete(
+ self,
+ grant_id: str,
+ *,
+ account_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[IdPFederationGrantDeleteResponse]:
+ """Deletes an IdP federation grant.
+
+ The identity provider remains in the account,
+ but it is no longer available for federation to other accounts.
+
+ Args:
+ account_id: Identifier.
+
+ grant_id: Identifier.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not grant_id:
+ raise ValueError(f"Expected a non-empty value for `grant_id` but received {grant_id!r}")
+ return self._delete(
+ path_template(
+ "/accounts/{account_id}/access/idp_federation_grants/{grant_id}",
+ account_id=account_id,
+ grant_id=grant_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[IdPFederationGrantDeleteResponse]]._unwrapper,
+ ),
+ cast_to=cast(
+ Type[Optional[IdPFederationGrantDeleteResponse]], ResultWrapper[IdPFederationGrantDeleteResponse]
+ ),
+ )
+
+ def get(
+ self,
+ grant_id: str,
+ *,
+ account_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[IdPFederationGrant]:
+ """
+ Retrieves a single IdP federation grant by its UID.
+
+ Args:
+ account_id: Identifier.
+
+ grant_id: Identifier.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not grant_id:
+ raise ValueError(f"Expected a non-empty value for `grant_id` but received {grant_id!r}")
+ return self._get(
+ path_template(
+ "/accounts/{account_id}/access/idp_federation_grants/{grant_id}",
+ account_id=account_id,
+ grant_id=grant_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[IdPFederationGrant]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[IdPFederationGrant]], ResultWrapper[IdPFederationGrant]),
+ )
+
+
+class AsyncIdPFederationGrantsResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncIdPFederationGrantsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncIdPFederationGrantsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncIdPFederationGrantsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#with_streaming_response
+ """
+ return AsyncIdPFederationGrantsResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ account_id: str,
+ idp_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[IdPFederationGrant]:
+ """
+ Creates an IdP federation grant for the specified identity provider, making it
+ available for federation to other accounts in the same Cloudflare organization.
+
+ The account must belong to a Cloudflare organization. One-time pin and
+ Cloudflare-managed identity providers cannot be federated. An identity provider
+ may only have one active grant at a time.
+
+ Args:
+ account_id: Identifier.
+
+ idp_id: UID of the identity provider to federate. Must be an existing identity provider
+ in this account. One-time pin and Cloudflare-managed identity providers cannot
+ be federated.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ return await self._post(
+ path_template("/accounts/{account_id}/access/idp_federation_grants", account_id=account_id),
+ body=await async_maybe_transform(
+ {"idp_id": idp_id}, idp_federation_grant_create_params.IdPFederationGrantCreateParams
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[IdPFederationGrant]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[IdPFederationGrant]], ResultWrapper[IdPFederationGrant]),
+ )
+
+ async def list(
+ self,
+ *,
+ account_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[IdPFederationGrantListResponse]:
+ """
+ Lists the IdP federation grants owned by the account.
+
+ Args:
+ account_id: Identifier.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ return await self._get(
+ path_template("/accounts/{account_id}/access/idp_federation_grants", account_id=account_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[IdPFederationGrantListResponse]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[IdPFederationGrantListResponse]], ResultWrapper[IdPFederationGrantListResponse]),
+ )
+
+ async def delete(
+ self,
+ grant_id: str,
+ *,
+ account_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[IdPFederationGrantDeleteResponse]:
+ """Deletes an IdP federation grant.
+
+ The identity provider remains in the account,
+ but it is no longer available for federation to other accounts.
+
+ Args:
+ account_id: Identifier.
+
+ grant_id: Identifier.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not grant_id:
+ raise ValueError(f"Expected a non-empty value for `grant_id` but received {grant_id!r}")
+ return await self._delete(
+ path_template(
+ "/accounts/{account_id}/access/idp_federation_grants/{grant_id}",
+ account_id=account_id,
+ grant_id=grant_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[IdPFederationGrantDeleteResponse]]._unwrapper,
+ ),
+ cast_to=cast(
+ Type[Optional[IdPFederationGrantDeleteResponse]], ResultWrapper[IdPFederationGrantDeleteResponse]
+ ),
+ )
+
+ async def get(
+ self,
+ grant_id: str,
+ *,
+ account_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[IdPFederationGrant]:
+ """
+ Retrieves a single IdP federation grant by its UID.
+
+ Args:
+ account_id: Identifier.
+
+ grant_id: Identifier.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not grant_id:
+ raise ValueError(f"Expected a non-empty value for `grant_id` but received {grant_id!r}")
+ return await self._get(
+ path_template(
+ "/accounts/{account_id}/access/idp_federation_grants/{grant_id}",
+ account_id=account_id,
+ grant_id=grant_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[IdPFederationGrant]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[IdPFederationGrant]], ResultWrapper[IdPFederationGrant]),
+ )
+
+
+class IdPFederationGrantsResourceWithRawResponse:
+ def __init__(self, idp_federation_grants: IdPFederationGrantsResource) -> None:
+ self._idp_federation_grants = idp_federation_grants
+
+ self.create = to_raw_response_wrapper(
+ idp_federation_grants.create,
+ )
+ self.list = to_raw_response_wrapper(
+ idp_federation_grants.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ idp_federation_grants.delete,
+ )
+ self.get = to_raw_response_wrapper(
+ idp_federation_grants.get,
+ )
+
+
+class AsyncIdPFederationGrantsResourceWithRawResponse:
+ def __init__(self, idp_federation_grants: AsyncIdPFederationGrantsResource) -> None:
+ self._idp_federation_grants = idp_federation_grants
+
+ self.create = async_to_raw_response_wrapper(
+ idp_federation_grants.create,
+ )
+ self.list = async_to_raw_response_wrapper(
+ idp_federation_grants.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ idp_federation_grants.delete,
+ )
+ self.get = async_to_raw_response_wrapper(
+ idp_federation_grants.get,
+ )
+
+
+class IdPFederationGrantsResourceWithStreamingResponse:
+ def __init__(self, idp_federation_grants: IdPFederationGrantsResource) -> None:
+ self._idp_federation_grants = idp_federation_grants
+
+ self.create = to_streamed_response_wrapper(
+ idp_federation_grants.create,
+ )
+ self.list = to_streamed_response_wrapper(
+ idp_federation_grants.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ idp_federation_grants.delete,
+ )
+ self.get = to_streamed_response_wrapper(
+ idp_federation_grants.get,
+ )
+
+
+class AsyncIdPFederationGrantsResourceWithStreamingResponse:
+ def __init__(self, idp_federation_grants: AsyncIdPFederationGrantsResource) -> None:
+ self._idp_federation_grants = idp_federation_grants
+
+ self.create = async_to_streamed_response_wrapper(
+ idp_federation_grants.create,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ idp_federation_grants.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ idp_federation_grants.delete,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ idp_federation_grants.get,
+ )
diff --git a/src/cloudflare/resources/zero_trust/api.md b/src/cloudflare/resources/zero_trust/api.md
index fae9fa9e7dc..6cdbf208ea3 100644
--- a/src/cloudflare/resources/zero_trust/api.md
+++ b/src/cloudflare/resources/zero_trust/api.md
@@ -498,6 +498,25 @@ Methods:
- client.zero_trust.access.gateway_ca.list(\*, account_id) -> SyncSinglePage[GatewayCAListResponse]
- client.zero_trust.access.gateway_ca.delete(certificate_id, \*, account_id) -> Optional[GatewayCADeleteResponse]
+### IdPFederationGrants
+
+Types:
+
+```python
+from cloudflare.types.zero_trust.access import (
+ IdPFederationGrant,
+ IdPFederationGrantListResponse,
+ IdPFederationGrantDeleteResponse,
+)
+```
+
+Methods:
+
+- client.zero_trust.access.idp_federation_grants.create(\*, account_id, \*\*params) -> Optional[IdPFederationGrant]
+- client.zero_trust.access.idp_federation_grants.list(\*, account_id) -> Optional[IdPFederationGrantListResponse]
+- client.zero_trust.access.idp_federation_grants.delete(grant_id, \*, account_id) -> Optional[IdPFederationGrantDeleteResponse]
+- client.zero_trust.access.idp_federation_grants.get(grant_id, \*, account_id) -> Optional[IdPFederationGrant]
+
### SAMLCertificates
Types:
diff --git a/src/cloudflare/types/zero_trust/access/__init__.py b/src/cloudflare/types/zero_trust/access/__init__.py
index cb7d98a5425..f1db601942e 100644
--- a/src/cloudflare/types/zero_trust/access/__init__.py
+++ b/src/cloudflare/types/zero_trust/access/__init__.py
@@ -47,6 +47,7 @@
from .tag_delete_response import TagDeleteResponse as TagDeleteResponse
from .approval_group_param import ApprovalGroupParam as ApprovalGroupParam
from .associated_hostnames import AssociatedHostnames as AssociatedHostnames
+from .idp_federation_grant import IdPFederationGrant as IdPFederationGrant
from .policy_create_params import PolicyCreateParams as PolicyCreateParams
from .policy_list_response import PolicyListResponse as PolicyListResponse
from .policy_update_params import PolicyUpdateParams as PolicyUpdateParams
@@ -94,6 +95,9 @@
from .saml_certificate_list_response import SAMLCertificateListResponse as SAMLCertificateListResponse
from .saml_certificate_rotate_response import SAMLCertificateRotateResponse as SAMLCertificateRotateResponse
from .scim_config_authentication_oauth2 import SCIMConfigAuthenticationOauth2 as SCIMConfigAuthenticationOauth2
+from .idp_federation_grant_create_params import IdPFederationGrantCreateParams as IdPFederationGrantCreateParams
+from .idp_federation_grant_list_response import IdPFederationGrantListResponse as IdPFederationGrantListResponse
+from .idp_federation_grant_delete_response import IdPFederationGrantDeleteResponse as IdPFederationGrantDeleteResponse
from .scim_config_authentication_http_basic import (
SCIMConfigAuthenticationHTTPBasic as SCIMConfigAuthenticationHTTPBasic,
)
diff --git a/src/cloudflare/types/zero_trust/access/idp_federation_grant.py b/src/cloudflare/types/zero_trust/access/idp_federation_grant.py
new file mode 100644
index 00000000000..c9bd2c2bda6
--- /dev/null
+++ b/src/cloudflare/types/zero_trust/access/idp_federation_grant.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from datetime import datetime
+
+from ...._models import BaseModel
+
+__all__ = ["IdPFederationGrant"]
+
+
+class IdPFederationGrant(BaseModel):
+ id: str
+ """UID of the IdP federation grant."""
+
+ created_at: datetime
+ """When the grant was created."""
+
+ idp_id: str
+ """UID of the identity provider being federated."""
diff --git a/src/cloudflare/types/zero_trust/access/idp_federation_grant_create_params.py b/src/cloudflare/types/zero_trust/access/idp_federation_grant_create_params.py
new file mode 100644
index 00000000000..8c305cc572b
--- /dev/null
+++ b/src/cloudflare/types/zero_trust/access/idp_federation_grant_create_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["IdPFederationGrantCreateParams"]
+
+
+class IdPFederationGrantCreateParams(TypedDict, total=False):
+ account_id: Required[str]
+ """Identifier."""
+
+ idp_id: Required[str]
+ """UID of the identity provider to federate.
+
+ Must be an existing identity provider in this account. One-time pin and
+ Cloudflare-managed identity providers cannot be federated.
+ """
diff --git a/src/cloudflare/types/zero_trust/access/idp_federation_grant_delete_response.py b/src/cloudflare/types/zero_trust/access/idp_federation_grant_delete_response.py
new file mode 100644
index 00000000000..768c2517ce1
--- /dev/null
+++ b/src/cloudflare/types/zero_trust/access/idp_federation_grant_delete_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ...._models import BaseModel
+
+__all__ = ["IdPFederationGrantDeleteResponse"]
+
+
+class IdPFederationGrantDeleteResponse(BaseModel):
+ id: Optional[str] = None
+ """UID of the deleted IdP federation grant."""
diff --git a/src/cloudflare/types/zero_trust/access/idp_federation_grant_list_response.py b/src/cloudflare/types/zero_trust/access/idp_federation_grant_list_response.py
new file mode 100644
index 00000000000..dfd7e168333
--- /dev/null
+++ b/src/cloudflare/types/zero_trust/access/idp_federation_grant_list_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .idp_federation_grant import IdPFederationGrant
+
+__all__ = ["IdPFederationGrantListResponse"]
+
+IdPFederationGrantListResponse: TypeAlias = List[IdPFederationGrant]
diff --git a/tests/api_resources/zero_trust/access/test_idp_federation_grants.py b/tests/api_resources/zero_trust/access/test_idp_federation_grants.py
new file mode 100644
index 00000000000..ab5896d9356
--- /dev/null
+++ b/tests/api_resources/zero_trust/access/test_idp_federation_grants.py
@@ -0,0 +1,380 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, Optional, cast
+
+import pytest
+
+from cloudflare import Cloudflare, AsyncCloudflare
+from tests.utils import assert_matches_type
+from cloudflare.types.zero_trust.access import (
+ IdPFederationGrant,
+ IdPFederationGrantListResponse,
+ IdPFederationGrantDeleteResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestIdPFederationGrants:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: Cloudflare) -> None:
+ idp_federation_grant = client.zero_trust.access.idp_federation_grants.create(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ idp_id="a79de439-0e7f-4ebb-8a02-222222222222",
+ )
+ assert_matches_type(Optional[IdPFederationGrant], idp_federation_grant, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: Cloudflare) -> None:
+ response = client.zero_trust.access.idp_federation_grants.with_raw_response.create(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ idp_id="a79de439-0e7f-4ebb-8a02-222222222222",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ idp_federation_grant = response.parse()
+ assert_matches_type(Optional[IdPFederationGrant], idp_federation_grant, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: Cloudflare) -> None:
+ with client.zero_trust.access.idp_federation_grants.with_streaming_response.create(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ idp_id="a79de439-0e7f-4ebb-8a02-222222222222",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ idp_federation_grant = response.parse()
+ assert_matches_type(Optional[IdPFederationGrant], idp_federation_grant, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_create(self, client: Cloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ client.zero_trust.access.idp_federation_grants.with_raw_response.create(
+ account_id="",
+ idp_id="a79de439-0e7f-4ebb-8a02-222222222222",
+ )
+
+ @parametrize
+ def test_method_list(self, client: Cloudflare) -> None:
+ idp_federation_grant = client.zero_trust.access.idp_federation_grants.list(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+ assert_matches_type(Optional[IdPFederationGrantListResponse], idp_federation_grant, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Cloudflare) -> None:
+ response = client.zero_trust.access.idp_federation_grants.with_raw_response.list(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ idp_federation_grant = response.parse()
+ assert_matches_type(Optional[IdPFederationGrantListResponse], idp_federation_grant, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Cloudflare) -> None:
+ with client.zero_trust.access.idp_federation_grants.with_streaming_response.list(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ idp_federation_grant = response.parse()
+ assert_matches_type(Optional[IdPFederationGrantListResponse], idp_federation_grant, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_list(self, client: Cloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ client.zero_trust.access.idp_federation_grants.with_raw_response.list(
+ account_id="",
+ )
+
+ @parametrize
+ def test_method_delete(self, client: Cloudflare) -> None:
+ idp_federation_grant = client.zero_trust.access.idp_federation_grants.delete(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+ assert_matches_type(Optional[IdPFederationGrantDeleteResponse], idp_federation_grant, path=["response"])
+
+ @parametrize
+ def test_raw_response_delete(self, client: Cloudflare) -> None:
+ response = client.zero_trust.access.idp_federation_grants.with_raw_response.delete(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ idp_federation_grant = response.parse()
+ assert_matches_type(Optional[IdPFederationGrantDeleteResponse], idp_federation_grant, path=["response"])
+
+ @parametrize
+ def test_streaming_response_delete(self, client: Cloudflare) -> None:
+ with client.zero_trust.access.idp_federation_grants.with_streaming_response.delete(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ idp_federation_grant = response.parse()
+ assert_matches_type(Optional[IdPFederationGrantDeleteResponse], idp_federation_grant, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_delete(self, client: Cloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ client.zero_trust.access.idp_federation_grants.with_raw_response.delete(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `grant_id` but received ''"):
+ client.zero_trust.access.idp_federation_grants.with_raw_response.delete(
+ grant_id="",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+
+ @parametrize
+ def test_method_get(self, client: Cloudflare) -> None:
+ idp_federation_grant = client.zero_trust.access.idp_federation_grants.get(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+ assert_matches_type(Optional[IdPFederationGrant], idp_federation_grant, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Cloudflare) -> None:
+ response = client.zero_trust.access.idp_federation_grants.with_raw_response.get(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ idp_federation_grant = response.parse()
+ assert_matches_type(Optional[IdPFederationGrant], idp_federation_grant, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Cloudflare) -> None:
+ with client.zero_trust.access.idp_federation_grants.with_streaming_response.get(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ idp_federation_grant = response.parse()
+ assert_matches_type(Optional[IdPFederationGrant], idp_federation_grant, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Cloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ client.zero_trust.access.idp_federation_grants.with_raw_response.get(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `grant_id` but received ''"):
+ client.zero_trust.access.idp_federation_grants.with_raw_response.get(
+ grant_id="",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+
+
+class TestAsyncIdPFederationGrants:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncCloudflare) -> None:
+ idp_federation_grant = await async_client.zero_trust.access.idp_federation_grants.create(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ idp_id="a79de439-0e7f-4ebb-8a02-222222222222",
+ )
+ assert_matches_type(Optional[IdPFederationGrant], idp_federation_grant, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncCloudflare) -> None:
+ response = await async_client.zero_trust.access.idp_federation_grants.with_raw_response.create(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ idp_id="a79de439-0e7f-4ebb-8a02-222222222222",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ idp_federation_grant = await response.parse()
+ assert_matches_type(Optional[IdPFederationGrant], idp_federation_grant, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncCloudflare) -> None:
+ async with async_client.zero_trust.access.idp_federation_grants.with_streaming_response.create(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ idp_id="a79de439-0e7f-4ebb-8a02-222222222222",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ idp_federation_grant = await response.parse()
+ assert_matches_type(Optional[IdPFederationGrant], idp_federation_grant, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_create(self, async_client: AsyncCloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ await async_client.zero_trust.access.idp_federation_grants.with_raw_response.create(
+ account_id="",
+ idp_id="a79de439-0e7f-4ebb-8a02-222222222222",
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncCloudflare) -> None:
+ idp_federation_grant = await async_client.zero_trust.access.idp_federation_grants.list(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+ assert_matches_type(Optional[IdPFederationGrantListResponse], idp_federation_grant, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncCloudflare) -> None:
+ response = await async_client.zero_trust.access.idp_federation_grants.with_raw_response.list(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ idp_federation_grant = await response.parse()
+ assert_matches_type(Optional[IdPFederationGrantListResponse], idp_federation_grant, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncCloudflare) -> None:
+ async with async_client.zero_trust.access.idp_federation_grants.with_streaming_response.list(
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ idp_federation_grant = await response.parse()
+ assert_matches_type(Optional[IdPFederationGrantListResponse], idp_federation_grant, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_list(self, async_client: AsyncCloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ await async_client.zero_trust.access.idp_federation_grants.with_raw_response.list(
+ account_id="",
+ )
+
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncCloudflare) -> None:
+ idp_federation_grant = await async_client.zero_trust.access.idp_federation_grants.delete(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+ assert_matches_type(Optional[IdPFederationGrantDeleteResponse], idp_federation_grant, path=["response"])
+
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncCloudflare) -> None:
+ response = await async_client.zero_trust.access.idp_federation_grants.with_raw_response.delete(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ idp_federation_grant = await response.parse()
+ assert_matches_type(Optional[IdPFederationGrantDeleteResponse], idp_federation_grant, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncCloudflare) -> None:
+ async with async_client.zero_trust.access.idp_federation_grants.with_streaming_response.delete(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ idp_federation_grant = await response.parse()
+ assert_matches_type(Optional[IdPFederationGrantDeleteResponse], idp_federation_grant, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncCloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ await async_client.zero_trust.access.idp_federation_grants.with_raw_response.delete(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `grant_id` but received ''"):
+ await async_client.zero_trust.access.idp_federation_grants.with_raw_response.delete(
+ grant_id="",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncCloudflare) -> None:
+ idp_federation_grant = await async_client.zero_trust.access.idp_federation_grants.get(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+ assert_matches_type(Optional[IdPFederationGrant], idp_federation_grant, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncCloudflare) -> None:
+ response = await async_client.zero_trust.access.idp_federation_grants.with_raw_response.get(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ idp_federation_grant = await response.parse()
+ assert_matches_type(Optional[IdPFederationGrant], idp_federation_grant, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncCloudflare) -> None:
+ async with async_client.zero_trust.access.idp_federation_grants.with_streaming_response.get(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ idp_federation_grant = await response.parse()
+ assert_matches_type(Optional[IdPFederationGrant], idp_federation_grant, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncCloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ await async_client.zero_trust.access.idp_federation_grants.with_raw_response.get(
+ grant_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `grant_id` but received ''"):
+ await async_client.zero_trust.access.idp_federation_grants.with_raw_response.get(
+ grant_id="",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ )
From 46c3cec8250cd2498e09c51aec2e7040cb8e25f1 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Jun 2026 19:53:33 +0000
Subject: [PATCH 13/18] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 99ffe46629d..f7c1db77698 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 2323
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-59bd5e43dfef7cf91cec9008063c948951ab4a8b7614c09b916c226bc737c532.yml
-openapi_spec_hash: 3005ae41716ac63d8ce8507844e6c71c
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-baa9c57c8cec8fa2999dd365dc419d59f0c5be8b772344ceb722e4cd08563c6f.yml
+openapi_spec_hash: 63308a10826084e5f64b82f361d8dcd2
config_hash: d356305f9e4bef414359ff0156867a23
From ab254121224485839c1f3c1da11ad3a9b83ed120 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Jun 2026 21:17:34 +0000
Subject: [PATCH 14/18] feat: feat(api): sync workflows API endpoints (2 new)
* feat(api): sync workflows API endpoints (2 new)
* chore: drop TODO(linguini) stub comments
* chore: drop instances/terminate stub (now skipped from sync)
* chore: sync workflows API endpoints into stainless config
---
.stats.yml | 6 +-
src/cloudflare/resources/workflows/api.md | 5 +-
.../workflows/instances/instances.py | 169 +++++++-
.../resources/workflows/versions.py | 109 +++++
src/cloudflare/types/workflows/__init__.py | 3 +
.../types/workflows/instance_step_params.py | 28 ++
.../types/workflows/instance_step_response.py | 33 ++
.../types/workflows/version_graph_response.py | 377 ++++++++++++++++++
.../api_resources/workflows/test_instances.py | 169 ++++++++
.../api_resources/workflows/test_versions.py | 126 +++++-
10 files changed, 1019 insertions(+), 6 deletions(-)
create mode 100644 src/cloudflare/types/workflows/instance_step_params.py
create mode 100644 src/cloudflare/types/workflows/instance_step_response.py
create mode 100644 src/cloudflare/types/workflows/version_graph_response.py
diff --git a/.stats.yml b/.stats.yml
index f7c1db77698..22230624e3d 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 2323
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-baa9c57c8cec8fa2999dd365dc419d59f0c5be8b772344ceb722e4cd08563c6f.yml
+configured_endpoints: 2325
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-eeab7a57d84f76af6bb611b6eb115f3ab818505d7f8d09c35751d4ba4bb0788e.yml
openapi_spec_hash: 63308a10826084e5f64b82f361d8dcd2
-config_hash: d356305f9e4bef414359ff0156867a23
+config_hash: 3e02eec9f8dbb1c042399a4bea3363d2
diff --git a/src/cloudflare/resources/workflows/api.md b/src/cloudflare/resources/workflows/api.md
index a90bde50ec2..9b6d98404ce 100644
--- a/src/cloudflare/resources/workflows/api.md
+++ b/src/cloudflare/resources/workflows/api.md
@@ -28,6 +28,7 @@ from cloudflare.types.workflows import (
InstanceListResponse,
InstanceBulkResponse,
InstanceGetResponse,
+ InstanceStepResponse,
)
```
@@ -37,6 +38,7 @@ Methods:
- client.workflows.instances.list(workflow_name, \*, account_id, \*\*params) -> SyncV4PagePaginationArray[InstanceListResponse]
- client.workflows.instances.bulk(workflow_name, \*, account_id, \*\*params) -> SyncSinglePage[InstanceBulkResponse]
- client.workflows.instances.get(instance_id, \*, account_id, workflow_name, \*\*params) -> InstanceGetResponse
+- client.workflows.instances.step(instance_id, \*, account_id, workflow_name, \*\*params) -> InstanceStepResponse
### Status
@@ -61,10 +63,11 @@ Methods:
Types:
```python
-from cloudflare.types.workflows import VersionListResponse, VersionGetResponse
+from cloudflare.types.workflows import VersionListResponse, VersionGetResponse, VersionGraphResponse
```
Methods:
- client.workflows.versions.list(workflow_name, \*, account_id, \*\*params) -> SyncV4PagePaginationArray[VersionListResponse]
- client.workflows.versions.get(version_id, \*, account_id, workflow_name) -> VersionGetResponse
+- client.workflows.versions.graph(version_id, \*, account_id, workflow_name) -> VersionGraphResponse
diff --git a/src/cloudflare/resources/workflows/instances/instances.py b/src/cloudflare/resources/workflows/instances/instances.py
index c0a88c923a9..fe90af5e81b 100644
--- a/src/cloudflare/resources/workflows/instances/instances.py
+++ b/src/cloudflare/resources/workflows/instances/instances.py
@@ -37,10 +37,17 @@
from ...._wrappers import ResultWrapper
from ....pagination import SyncSinglePage, AsyncSinglePage, SyncV4PagePaginationArray, AsyncV4PagePaginationArray
from ...._base_client import AsyncPaginator, make_request_options
-from ....types.workflows import instance_get_params, instance_bulk_params, instance_list_params, instance_create_params
+from ....types.workflows import (
+ instance_get_params,
+ instance_bulk_params,
+ instance_list_params,
+ instance_step_params,
+ instance_create_params,
+)
from ....types.workflows.instance_get_response import InstanceGetResponse
from ....types.workflows.instance_bulk_response import InstanceBulkResponse
from ....types.workflows.instance_list_response import InstanceListResponse
+from ....types.workflows.instance_step_response import InstanceStepResponse
from ....types.workflows.instance_create_response import InstanceCreateResponse
__all__ = ["InstancesResource", "AsyncInstancesResource"]
@@ -318,6 +325,80 @@ def get(
cast_to=cast(Type[InstanceGetResponse], ResultWrapper[InstanceGetResponse]),
)
+ def step(
+ self,
+ instance_id: str,
+ *,
+ account_id: str,
+ workflow_name: str,
+ name: str,
+ type: Literal["step", "waitForEvent"],
+ attempt: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> InstanceStepResponse:
+ """
+ Retrieves the full, untruncated output for a specific step on a workflow
+ instance. Returns a flat status-shaped JSON body with step `status` ('running' |
+ 'waiting' | 'complete' | 'errored'), `error` (nullable), and `output` (the step
+ value, or null while running/waiting/errored). When the step returned a
+ ReadableStream from step.do, the response is served as
+ 'application/octet-stream' with the raw bytes as the body instead of JSON. A
+ `status='running'` response with non-null `error` indicates the step is
+ currently retrying after a prior attempt failed.
+
+ Args:
+ name: Exact step name from the instance logs response, including the generated counter
+ suffix.
+
+ type: Step type to disambiguate step.do and waitForEvent entries that share the same
+ name.
+
+ attempt: Specific attempt number to retrieve output or error for.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not workflow_name:
+ raise ValueError(f"Expected a non-empty value for `workflow_name` but received {workflow_name!r}")
+ if not instance_id:
+ raise ValueError(f"Expected a non-empty value for `instance_id` but received {instance_id!r}")
+ return self._get(
+ path_template(
+ "/accounts/{account_id}/workflows/{workflow_name}/instances/{instance_id}/step",
+ account_id=account_id,
+ workflow_name=workflow_name,
+ instance_id=instance_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "name": name,
+ "type": type,
+ "attempt": attempt,
+ },
+ instance_step_params.InstanceStepParams,
+ ),
+ post_parser=ResultWrapper[InstanceStepResponse]._unwrapper,
+ ),
+ cast_to=cast(Type[InstanceStepResponse], ResultWrapper[InstanceStepResponse]),
+ )
+
class AsyncInstancesResource(AsyncAPIResource):
@cached_property
@@ -591,6 +672,80 @@ async def get(
cast_to=cast(Type[InstanceGetResponse], ResultWrapper[InstanceGetResponse]),
)
+ async def step(
+ self,
+ instance_id: str,
+ *,
+ account_id: str,
+ workflow_name: str,
+ name: str,
+ type: Literal["step", "waitForEvent"],
+ attempt: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> InstanceStepResponse:
+ """
+ Retrieves the full, untruncated output for a specific step on a workflow
+ instance. Returns a flat status-shaped JSON body with step `status` ('running' |
+ 'waiting' | 'complete' | 'errored'), `error` (nullable), and `output` (the step
+ value, or null while running/waiting/errored). When the step returned a
+ ReadableStream from step.do, the response is served as
+ 'application/octet-stream' with the raw bytes as the body instead of JSON. A
+ `status='running'` response with non-null `error` indicates the step is
+ currently retrying after a prior attempt failed.
+
+ Args:
+ name: Exact step name from the instance logs response, including the generated counter
+ suffix.
+
+ type: Step type to disambiguate step.do and waitForEvent entries that share the same
+ name.
+
+ attempt: Specific attempt number to retrieve output or error for.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not workflow_name:
+ raise ValueError(f"Expected a non-empty value for `workflow_name` but received {workflow_name!r}")
+ if not instance_id:
+ raise ValueError(f"Expected a non-empty value for `instance_id` but received {instance_id!r}")
+ return await self._get(
+ path_template(
+ "/accounts/{account_id}/workflows/{workflow_name}/instances/{instance_id}/step",
+ account_id=account_id,
+ workflow_name=workflow_name,
+ instance_id=instance_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "name": name,
+ "type": type,
+ "attempt": attempt,
+ },
+ instance_step_params.InstanceStepParams,
+ ),
+ post_parser=ResultWrapper[InstanceStepResponse]._unwrapper,
+ ),
+ cast_to=cast(Type[InstanceStepResponse], ResultWrapper[InstanceStepResponse]),
+ )
+
class InstancesResourceWithRawResponse:
def __init__(self, instances: InstancesResource) -> None:
@@ -608,6 +763,9 @@ def __init__(self, instances: InstancesResource) -> None:
self.get = to_raw_response_wrapper(
instances.get,
)
+ self.step = to_raw_response_wrapper(
+ instances.step,
+ )
@cached_property
def status(self) -> StatusResourceWithRawResponse:
@@ -634,6 +792,9 @@ def __init__(self, instances: AsyncInstancesResource) -> None:
self.get = async_to_raw_response_wrapper(
instances.get,
)
+ self.step = async_to_raw_response_wrapper(
+ instances.step,
+ )
@cached_property
def status(self) -> AsyncStatusResourceWithRawResponse:
@@ -660,6 +821,9 @@ def __init__(self, instances: InstancesResource) -> None:
self.get = to_streamed_response_wrapper(
instances.get,
)
+ self.step = to_streamed_response_wrapper(
+ instances.step,
+ )
@cached_property
def status(self) -> StatusResourceWithStreamingResponse:
@@ -686,6 +850,9 @@ def __init__(self, instances: AsyncInstancesResource) -> None:
self.get = async_to_streamed_response_wrapper(
instances.get,
)
+ self.step = async_to_streamed_response_wrapper(
+ instances.step,
+ )
@cached_property
def status(self) -> AsyncStatusResourceWithStreamingResponse:
diff --git a/src/cloudflare/resources/workflows/versions.py b/src/cloudflare/resources/workflows/versions.py
index b702774c237..51002d88919 100644
--- a/src/cloudflare/resources/workflows/versions.py
+++ b/src/cloudflare/resources/workflows/versions.py
@@ -22,6 +22,7 @@
from ...types.workflows import version_list_params
from ...types.workflows.version_get_response import VersionGetResponse
from ...types.workflows.version_list_response import VersionListResponse
+from ...types.workflows.version_graph_response import VersionGraphResponse
__all__ = ["VersionsResource", "AsyncVersionsResource"]
@@ -147,6 +148,54 @@ def get(
cast_to=cast(Type[VersionGetResponse], ResultWrapper[VersionGetResponse]),
)
+ def graph(
+ self,
+ version_id: str,
+ *,
+ account_id: str,
+ workflow_name: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VersionGraphResponse:
+ """
+ Retrieves the graph visualization of a workflow version.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not workflow_name:
+ raise ValueError(f"Expected a non-empty value for `workflow_name` but received {workflow_name!r}")
+ if not version_id:
+ raise ValueError(f"Expected a non-empty value for `version_id` but received {version_id!r}")
+ return self._get(
+ path_template(
+ "/accounts/{account_id}/workflows/{workflow_name}/versions/{version_id}/graph",
+ account_id=account_id,
+ workflow_name=workflow_name,
+ version_id=version_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[VersionGraphResponse]._unwrapper,
+ ),
+ cast_to=cast(Type[VersionGraphResponse], ResultWrapper[VersionGraphResponse]),
+ )
+
class AsyncVersionsResource(AsyncAPIResource):
@cached_property
@@ -269,6 +318,54 @@ async def get(
cast_to=cast(Type[VersionGetResponse], ResultWrapper[VersionGetResponse]),
)
+ async def graph(
+ self,
+ version_id: str,
+ *,
+ account_id: str,
+ workflow_name: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VersionGraphResponse:
+ """
+ Retrieves the graph visualization of a workflow version.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not workflow_name:
+ raise ValueError(f"Expected a non-empty value for `workflow_name` but received {workflow_name!r}")
+ if not version_id:
+ raise ValueError(f"Expected a non-empty value for `version_id` but received {version_id!r}")
+ return await self._get(
+ path_template(
+ "/accounts/{account_id}/workflows/{workflow_name}/versions/{version_id}/graph",
+ account_id=account_id,
+ workflow_name=workflow_name,
+ version_id=version_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[VersionGraphResponse]._unwrapper,
+ ),
+ cast_to=cast(Type[VersionGraphResponse], ResultWrapper[VersionGraphResponse]),
+ )
+
class VersionsResourceWithRawResponse:
def __init__(self, versions: VersionsResource) -> None:
@@ -280,6 +377,9 @@ def __init__(self, versions: VersionsResource) -> None:
self.get = to_raw_response_wrapper(
versions.get,
)
+ self.graph = to_raw_response_wrapper(
+ versions.graph,
+ )
class AsyncVersionsResourceWithRawResponse:
@@ -292,6 +392,9 @@ def __init__(self, versions: AsyncVersionsResource) -> None:
self.get = async_to_raw_response_wrapper(
versions.get,
)
+ self.graph = async_to_raw_response_wrapper(
+ versions.graph,
+ )
class VersionsResourceWithStreamingResponse:
@@ -304,6 +407,9 @@ def __init__(self, versions: VersionsResource) -> None:
self.get = to_streamed_response_wrapper(
versions.get,
)
+ self.graph = to_streamed_response_wrapper(
+ versions.graph,
+ )
class AsyncVersionsResourceWithStreamingResponse:
@@ -316,3 +422,6 @@ def __init__(self, versions: AsyncVersionsResource) -> None:
self.get = async_to_streamed_response_wrapper(
versions.get,
)
+ self.graph = async_to_streamed_response_wrapper(
+ versions.graph,
+ )
diff --git a/src/cloudflare/types/workflows/__init__.py b/src/cloudflare/types/workflows/__init__.py
index 385d8fff853..7bcec65237c 100644
--- a/src/cloudflare/types/workflows/__init__.py
+++ b/src/cloudflare/types/workflows/__init__.py
@@ -6,6 +6,7 @@
from .version_list_params import VersionListParams as VersionListParams
from .instance_bulk_params import InstanceBulkParams as InstanceBulkParams
from .instance_list_params import InstanceListParams as InstanceListParams
+from .instance_step_params import InstanceStepParams as InstanceStepParams
from .version_get_response import VersionGetResponse as VersionGetResponse
from .workflow_list_params import WorkflowListParams as WorkflowListParams
from .instance_get_response import InstanceGetResponse as InstanceGetResponse
@@ -14,6 +15,8 @@
from .instance_bulk_response import InstanceBulkResponse as InstanceBulkResponse
from .instance_create_params import InstanceCreateParams as InstanceCreateParams
from .instance_list_response import InstanceListResponse as InstanceListResponse
+from .instance_step_response import InstanceStepResponse as InstanceStepResponse
+from .version_graph_response import VersionGraphResponse as VersionGraphResponse
from .workflow_list_response import WorkflowListResponse as WorkflowListResponse
from .workflow_update_params import WorkflowUpdateParams as WorkflowUpdateParams
from .instance_create_response import InstanceCreateResponse as InstanceCreateResponse
diff --git a/src/cloudflare/types/workflows/instance_step_params.py b/src/cloudflare/types/workflows/instance_step_params.py
new file mode 100644
index 00000000000..e1eafb3acf5
--- /dev/null
+++ b/src/cloudflare/types/workflows/instance_step_params.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["InstanceStepParams"]
+
+
+class InstanceStepParams(TypedDict, total=False):
+ account_id: Required[str]
+
+ workflow_name: Required[str]
+
+ name: Required[str]
+ """
+ Exact step name from the instance logs response, including the generated counter
+ suffix.
+ """
+
+ type: Required[Literal["step", "waitForEvent"]]
+ """
+ Step type to disambiguate step.do and waitForEvent entries that share the same
+ name.
+ """
+
+ attempt: int
+ """Specific attempt number to retrieve output or error for."""
diff --git a/src/cloudflare/types/workflows/instance_step_response.py b/src/cloudflare/types/workflows/instance_step_response.py
new file mode 100644
index 00000000000..d91e7202d14
--- /dev/null
+++ b/src/cloudflare/types/workflows/instance_step_response.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["InstanceStepResponse", "Error"]
+
+
+class Error(BaseModel):
+ """Error details when status='errored'; null otherwise."""
+
+ message: str
+
+ name: str
+
+
+class InstanceStepResponse(BaseModel):
+ error: Optional[Error] = None
+ """Error details when status='errored'; null otherwise."""
+
+ status: Literal[
+ "queued", "running", "paused", "errored", "terminated", "complete", "waitingForPause", "waiting", "rollingBack"
+ ]
+
+ output: Optional[object] = None
+ """Full step output or waitForEvent payload without truncation.
+
+ Sensitive outputs are returned as '[REDACTED]'. Populated when
+ status='complete'. May be a ReadableStream when the step returned one from
+ step.do; stream outputs are served as application/octet-stream rather than JSON.
+ """
diff --git a/src/cloudflare/types/workflows/version_graph_response.py b/src/cloudflare/types/workflows/version_graph_response.py
new file mode 100644
index 00000000000..71070ac4789
--- /dev/null
+++ b/src/cloudflare/types/workflows/version_graph_response.py
@@ -0,0 +1,377 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal, TypeAlias
+
+from ..._models import BaseModel
+
+__all__ = [
+ "VersionGraphResponse",
+ "Graph",
+ "GraphWorkflow",
+ "GraphWorkflowFunctions",
+ "GraphWorkflowNode",
+ "GraphWorkflowNodeUnionMember0",
+ "GraphWorkflowNodeUnionMember1",
+ "GraphWorkflowNodeUnionMember1Config",
+ "GraphWorkflowNodeUnionMember1ConfigRetries",
+ "GraphWorkflowNodeUnionMember2",
+ "GraphWorkflowNodeUnionMember2Options",
+ "GraphWorkflowNodeUnionMember2Payload",
+ "GraphWorkflowNodeUnionMember2PayloadType",
+ "GraphWorkflowNodeUnionMember2PayloadUnionMember1",
+ "GraphWorkflowNodeUnionMember3",
+ "GraphWorkflowNodeUnionMember4",
+ "GraphWorkflowNodeUnionMember5",
+ "GraphWorkflowNodeUnionMember6",
+ "GraphWorkflowNodeUnionMember6CatchBlock",
+ "GraphWorkflowNodeUnionMember6FinallyBlock",
+ "GraphWorkflowNodeUnionMember6TryBlock",
+ "GraphWorkflowNodeUnionMember7",
+ "GraphWorkflowNodeUnionMember8",
+ "GraphWorkflowNodeUnionMember8Branch",
+ "GraphWorkflowNodeUnionMember9",
+ "GraphWorkflowNodeUnionMember9Branch",
+ "GraphWorkflowNodeUnionMember10",
+ "GraphWorkflowNodeUnionMember10Functions",
+ "GraphWorkflowNodeUnionMember10Payload",
+ "GraphWorkflowNodeUnionMember10PayloadType",
+ "GraphWorkflowNodeUnionMember10PayloadUnionMember1",
+ "GraphWorkflowNodeUnionMember11",
+ "GraphWorkflowNodeUnionMember12",
+ "GraphWorkflowNodeUnionMember13",
+ "GraphWorkflowPayload",
+ "GraphWorkflowPayloadType",
+ "GraphWorkflowPayloadUnionMember1",
+]
+
+
+class GraphWorkflowFunctions(BaseModel):
+ name: str
+
+ nodes: List[object]
+ """Child nodes (recursive)."""
+
+ type: Literal["function_def"]
+
+
+class GraphWorkflowNodeUnionMember0(BaseModel):
+ duration: Union[float, str]
+ """Duration as milliseconds (number) or human-readable string."""
+
+ name: str
+
+ type: Literal["step_sleep"]
+
+ resolves: Optional[float] = None
+
+ starts: Optional[float] = None
+
+
+class GraphWorkflowNodeUnionMember1ConfigRetries(BaseModel):
+ """Retry policy for a step."""
+
+ backoff: Literal["constant", "linear", "exponential"]
+ """Backoff strategy for step retries."""
+
+ delay: Union[float, str]
+ """Duration as milliseconds (number) or human-readable string."""
+
+ limit: float
+
+
+class GraphWorkflowNodeUnionMember1Config(BaseModel):
+ """Configuration for a step (retries and timeout)."""
+
+ retries: GraphWorkflowNodeUnionMember1ConfigRetries
+ """Retry policy for a step."""
+
+ timeout: Union[float, str]
+ """Duration as milliseconds (number) or human-readable string."""
+
+
+class GraphWorkflowNodeUnionMember1(BaseModel):
+ config: GraphWorkflowNodeUnionMember1Config
+ """Configuration for a step (retries and timeout)."""
+
+ name: str
+
+ nodes: List[object]
+ """Child nodes (recursive)."""
+
+ type: Literal["step_do"]
+
+ resolves: Optional[float] = None
+
+ starts: Optional[float] = None
+
+
+class GraphWorkflowNodeUnionMember2Options(BaseModel):
+ """Options for a waitForEvent step."""
+
+ event_type: str
+
+ timeout: Union[float, str]
+ """Duration as milliseconds (number) or human-readable string."""
+
+
+class GraphWorkflowNodeUnionMember2PayloadType(BaseModel):
+ type: Literal["unknown"]
+
+
+class GraphWorkflowNodeUnionMember2PayloadUnionMember1(BaseModel):
+ fields: Dict[str, object]
+ """Nested JsonShape fields (recursive structure)."""
+
+ type: Literal["object"]
+
+
+GraphWorkflowNodeUnionMember2Payload: TypeAlias = Union[
+ GraphWorkflowNodeUnionMember2PayloadType, GraphWorkflowNodeUnionMember2PayloadUnionMember1
+]
+
+
+class GraphWorkflowNodeUnionMember2(BaseModel):
+ name: str
+
+ options: Optional[GraphWorkflowNodeUnionMember2Options] = None
+ """Options for a waitForEvent step."""
+
+ type: Literal["step_wait_for_event"]
+
+ payload: Optional[GraphWorkflowNodeUnionMember2Payload] = None
+ """Shape descriptor for JSON payloads."""
+
+ resolves: Optional[float] = None
+
+ starts: Optional[float] = None
+
+
+class GraphWorkflowNodeUnionMember3(BaseModel):
+ name: str
+
+ timestamp: str
+
+ type: Literal["step_sleep_until"]
+
+ resolves: Optional[float] = None
+
+ starts: Optional[float] = None
+
+
+class GraphWorkflowNodeUnionMember4(BaseModel):
+ nodes: List[object]
+ """Child nodes (recursive)."""
+
+ type: Literal["loop"]
+
+
+class GraphWorkflowNodeUnionMember5(BaseModel):
+ kind: Literal["all", "any", "all_settled", "race"]
+ """Parallel execution strategy."""
+
+ nodes: List[object]
+ """Child nodes (recursive)."""
+
+ type: Literal["parallel"]
+
+
+class GraphWorkflowNodeUnionMember6CatchBlock(BaseModel):
+ nodes: List[object]
+ """Child nodes (recursive)."""
+
+ type: Literal["block"]
+
+
+class GraphWorkflowNodeUnionMember6FinallyBlock(BaseModel):
+ nodes: List[object]
+ """Child nodes (recursive)."""
+
+ type: Literal["block"]
+
+
+class GraphWorkflowNodeUnionMember6TryBlock(BaseModel):
+ nodes: List[object]
+ """Child nodes (recursive)."""
+
+ type: Literal["block"]
+
+
+class GraphWorkflowNodeUnionMember6(BaseModel):
+ catch_block: Optional[GraphWorkflowNodeUnionMember6CatchBlock] = None
+
+ finally_block: Optional[GraphWorkflowNodeUnionMember6FinallyBlock] = None
+
+ try_block: Optional[GraphWorkflowNodeUnionMember6TryBlock] = None
+
+ type: Literal["try"]
+
+
+class GraphWorkflowNodeUnionMember7(BaseModel):
+ nodes: List[object]
+ """Child nodes (recursive)."""
+
+ type: Literal["block"]
+
+
+class GraphWorkflowNodeUnionMember8Branch(BaseModel):
+ condition: Optional[str] = None
+
+ nodes: List[object]
+ """Child nodes (recursive)."""
+
+
+class GraphWorkflowNodeUnionMember8(BaseModel):
+ branches: List[GraphWorkflowNodeUnionMember8Branch]
+
+ type: Literal["if"]
+
+
+class GraphWorkflowNodeUnionMember9Branch(BaseModel):
+ condition: Optional[str] = None
+
+ nodes: List[object]
+ """Child nodes (recursive)."""
+
+
+class GraphWorkflowNodeUnionMember9(BaseModel):
+ branches: List[GraphWorkflowNodeUnionMember9Branch]
+
+ discriminant: str
+
+ type: Literal["switch"]
+
+
+class GraphWorkflowNodeUnionMember10Functions(BaseModel):
+ name: str
+
+ nodes: List[object]
+ """Child nodes (recursive)."""
+
+ type: Literal["function_def"]
+
+
+class GraphWorkflowNodeUnionMember10PayloadType(BaseModel):
+ type: Literal["unknown"]
+
+
+class GraphWorkflowNodeUnionMember10PayloadUnionMember1(BaseModel):
+ fields: Dict[str, object]
+ """Nested JsonShape fields (recursive structure)."""
+
+ type: Literal["object"]
+
+
+GraphWorkflowNodeUnionMember10Payload: TypeAlias = Union[
+ GraphWorkflowNodeUnionMember10PayloadType, GraphWorkflowNodeUnionMember10PayloadUnionMember1
+]
+
+
+class GraphWorkflowNodeUnionMember10(BaseModel):
+ class_name: str
+
+ functions: Dict[str, GraphWorkflowNodeUnionMember10Functions]
+
+ nodes: List[object]
+ """Child nodes (recursive)."""
+
+ type: Literal["start"]
+
+ payload: Optional[GraphWorkflowNodeUnionMember10Payload] = None
+ """Shape descriptor for JSON payloads."""
+
+
+class GraphWorkflowNodeUnionMember11(BaseModel):
+ name: str
+
+ type: Literal["function_call"]
+
+ resolves: Optional[float] = None
+
+ starts: Optional[float] = None
+
+
+class GraphWorkflowNodeUnionMember12(BaseModel):
+ name: str
+
+ nodes: List[object]
+ """Child nodes (recursive)."""
+
+ type: Literal["function_def"]
+
+
+class GraphWorkflowNodeUnionMember13(BaseModel):
+ kind: Literal["break", "return"]
+ """Break or return from a loop."""
+
+ type: Literal["break"]
+
+
+GraphWorkflowNode: TypeAlias = Union[
+ GraphWorkflowNodeUnionMember0,
+ GraphWorkflowNodeUnionMember1,
+ GraphWorkflowNodeUnionMember2,
+ GraphWorkflowNodeUnionMember3,
+ GraphWorkflowNodeUnionMember4,
+ GraphWorkflowNodeUnionMember5,
+ GraphWorkflowNodeUnionMember6,
+ GraphWorkflowNodeUnionMember7,
+ GraphWorkflowNodeUnionMember8,
+ GraphWorkflowNodeUnionMember9,
+ GraphWorkflowNodeUnionMember10,
+ GraphWorkflowNodeUnionMember11,
+ GraphWorkflowNodeUnionMember12,
+ GraphWorkflowNodeUnionMember13,
+]
+
+
+class GraphWorkflowPayloadType(BaseModel):
+ type: Literal["unknown"]
+
+
+class GraphWorkflowPayloadUnionMember1(BaseModel):
+ fields: Dict[str, object]
+ """Nested JsonShape fields (recursive structure)."""
+
+ type: Literal["object"]
+
+
+GraphWorkflowPayload: TypeAlias = Union[GraphWorkflowPayloadType, GraphWorkflowPayloadUnionMember1]
+
+
+class GraphWorkflow(BaseModel):
+ """A parsed workflow entrypoint with its step graph."""
+
+ class_name: str
+
+ functions: Dict[str, GraphWorkflowFunctions]
+
+ nodes: List[GraphWorkflowNode]
+
+ payload: Optional[GraphWorkflowPayload] = None
+ """Shape descriptor for JSON payloads."""
+
+
+class Graph(BaseModel):
+ """Versioned workflow graph payload."""
+
+ version: float
+
+ workflow: GraphWorkflow
+ """A parsed workflow entrypoint with its step graph."""
+
+
+class VersionGraphResponse(BaseModel):
+ id: str
+
+ class_name: str
+
+ created_on: datetime
+
+ graph: Optional[Graph] = None
+ """Versioned workflow graph payload."""
+
+ modified_on: datetime
+
+ workflow_id: str
diff --git a/tests/api_resources/workflows/test_instances.py b/tests/api_resources/workflows/test_instances.py
index 5ffbec6aa23..b3f99e55b80 100644
--- a/tests/api_resources/workflows/test_instances.py
+++ b/tests/api_resources/workflows/test_instances.py
@@ -15,6 +15,7 @@
InstanceGetResponse,
InstanceBulkResponse,
InstanceListResponse,
+ InstanceStepResponse,
InstanceCreateResponse,
)
@@ -286,6 +287,90 @@ def test_path_params_get(self, client: Cloudflare) -> None:
workflow_name="x",
)
+ @parametrize
+ def test_method_step(self, client: Cloudflare) -> None:
+ instance = client.workflows.instances.step(
+ instance_id="x",
+ account_id="account_id",
+ workflow_name="x",
+ name="x",
+ type="step",
+ )
+ assert_matches_type(InstanceStepResponse, instance, path=["response"])
+
+ @parametrize
+ def test_method_step_with_all_params(self, client: Cloudflare) -> None:
+ instance = client.workflows.instances.step(
+ instance_id="x",
+ account_id="account_id",
+ workflow_name="x",
+ name="x",
+ type="step",
+ attempt=1,
+ )
+ assert_matches_type(InstanceStepResponse, instance, path=["response"])
+
+ @parametrize
+ def test_raw_response_step(self, client: Cloudflare) -> None:
+ response = client.workflows.instances.with_raw_response.step(
+ instance_id="x",
+ account_id="account_id",
+ workflow_name="x",
+ name="x",
+ type="step",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ instance = response.parse()
+ assert_matches_type(InstanceStepResponse, instance, path=["response"])
+
+ @parametrize
+ def test_streaming_response_step(self, client: Cloudflare) -> None:
+ with client.workflows.instances.with_streaming_response.step(
+ instance_id="x",
+ account_id="account_id",
+ workflow_name="x",
+ name="x",
+ type="step",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ instance = response.parse()
+ assert_matches_type(InstanceStepResponse, instance, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_step(self, client: Cloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ client.workflows.instances.with_raw_response.step(
+ instance_id="x",
+ account_id="",
+ workflow_name="x",
+ name="x",
+ type="step",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_name` but received ''"):
+ client.workflows.instances.with_raw_response.step(
+ instance_id="x",
+ account_id="account_id",
+ workflow_name="",
+ name="x",
+ type="step",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `instance_id` but received ''"):
+ client.workflows.instances.with_raw_response.step(
+ instance_id="",
+ account_id="account_id",
+ workflow_name="x",
+ name="x",
+ type="step",
+ )
+
class TestAsyncInstances:
parametrize = pytest.mark.parametrize(
@@ -553,3 +638,87 @@ async def test_path_params_get(self, async_client: AsyncCloudflare) -> None:
account_id="account_id",
workflow_name="x",
)
+
+ @parametrize
+ async def test_method_step(self, async_client: AsyncCloudflare) -> None:
+ instance = await async_client.workflows.instances.step(
+ instance_id="x",
+ account_id="account_id",
+ workflow_name="x",
+ name="x",
+ type="step",
+ )
+ assert_matches_type(InstanceStepResponse, instance, path=["response"])
+
+ @parametrize
+ async def test_method_step_with_all_params(self, async_client: AsyncCloudflare) -> None:
+ instance = await async_client.workflows.instances.step(
+ instance_id="x",
+ account_id="account_id",
+ workflow_name="x",
+ name="x",
+ type="step",
+ attempt=1,
+ )
+ assert_matches_type(InstanceStepResponse, instance, path=["response"])
+
+ @parametrize
+ async def test_raw_response_step(self, async_client: AsyncCloudflare) -> None:
+ response = await async_client.workflows.instances.with_raw_response.step(
+ instance_id="x",
+ account_id="account_id",
+ workflow_name="x",
+ name="x",
+ type="step",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ instance = await response.parse()
+ assert_matches_type(InstanceStepResponse, instance, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_step(self, async_client: AsyncCloudflare) -> None:
+ async with async_client.workflows.instances.with_streaming_response.step(
+ instance_id="x",
+ account_id="account_id",
+ workflow_name="x",
+ name="x",
+ type="step",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ instance = await response.parse()
+ assert_matches_type(InstanceStepResponse, instance, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_step(self, async_client: AsyncCloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ await async_client.workflows.instances.with_raw_response.step(
+ instance_id="x",
+ account_id="",
+ workflow_name="x",
+ name="x",
+ type="step",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_name` but received ''"):
+ await async_client.workflows.instances.with_raw_response.step(
+ instance_id="x",
+ account_id="account_id",
+ workflow_name="",
+ name="x",
+ type="step",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `instance_id` but received ''"):
+ await async_client.workflows.instances.with_raw_response.step(
+ instance_id="",
+ account_id="account_id",
+ workflow_name="x",
+ name="x",
+ type="step",
+ )
diff --git a/tests/api_resources/workflows/test_versions.py b/tests/api_resources/workflows/test_versions.py
index ecdc89ad65b..d1b3f39217a 100644
--- a/tests/api_resources/workflows/test_versions.py
+++ b/tests/api_resources/workflows/test_versions.py
@@ -10,7 +10,11 @@
from cloudflare import Cloudflare, AsyncCloudflare
from tests.utils import assert_matches_type
from cloudflare.pagination import SyncV4PagePaginationArray, AsyncV4PagePaginationArray
-from cloudflare.types.workflows import VersionGetResponse, VersionListResponse
+from cloudflare.types.workflows import (
+ VersionGetResponse,
+ VersionListResponse,
+ VersionGraphResponse,
+)
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -136,6 +140,66 @@ def test_path_params_get(self, client: Cloudflare) -> None:
workflow_name="x",
)
+ @parametrize
+ def test_method_graph(self, client: Cloudflare) -> None:
+ version = client.workflows.versions.graph(
+ version_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ account_id="account_id",
+ workflow_name="x",
+ )
+ assert_matches_type(VersionGraphResponse, version, path=["response"])
+
+ @parametrize
+ def test_raw_response_graph(self, client: Cloudflare) -> None:
+ response = client.workflows.versions.with_raw_response.graph(
+ version_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ account_id="account_id",
+ workflow_name="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ version = response.parse()
+ assert_matches_type(VersionGraphResponse, version, path=["response"])
+
+ @parametrize
+ def test_streaming_response_graph(self, client: Cloudflare) -> None:
+ with client.workflows.versions.with_streaming_response.graph(
+ version_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ account_id="account_id",
+ workflow_name="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ version = response.parse()
+ assert_matches_type(VersionGraphResponse, version, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_graph(self, client: Cloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ client.workflows.versions.with_raw_response.graph(
+ version_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ account_id="",
+ workflow_name="x",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_name` but received ''"):
+ client.workflows.versions.with_raw_response.graph(
+ version_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ account_id="account_id",
+ workflow_name="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `version_id` but received ''"):
+ client.workflows.versions.with_raw_response.graph(
+ version_id="",
+ account_id="account_id",
+ workflow_name="x",
+ )
+
class TestAsyncVersions:
parametrize = pytest.mark.parametrize(
@@ -259,3 +323,63 @@ async def test_path_params_get(self, async_client: AsyncCloudflare) -> None:
account_id="account_id",
workflow_name="x",
)
+
+ @parametrize
+ async def test_method_graph(self, async_client: AsyncCloudflare) -> None:
+ version = await async_client.workflows.versions.graph(
+ version_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ account_id="account_id",
+ workflow_name="x",
+ )
+ assert_matches_type(VersionGraphResponse, version, path=["response"])
+
+ @parametrize
+ async def test_raw_response_graph(self, async_client: AsyncCloudflare) -> None:
+ response = await async_client.workflows.versions.with_raw_response.graph(
+ version_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ account_id="account_id",
+ workflow_name="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ version = await response.parse()
+ assert_matches_type(VersionGraphResponse, version, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_graph(self, async_client: AsyncCloudflare) -> None:
+ async with async_client.workflows.versions.with_streaming_response.graph(
+ version_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ account_id="account_id",
+ workflow_name="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ version = await response.parse()
+ assert_matches_type(VersionGraphResponse, version, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_graph(self, async_client: AsyncCloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ await async_client.workflows.versions.with_raw_response.graph(
+ version_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ account_id="",
+ workflow_name="x",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_name` but received ''"):
+ await async_client.workflows.versions.with_raw_response.graph(
+ version_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ account_id="account_id",
+ workflow_name="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `version_id` but received ''"):
+ await async_client.workflows.versions.with_raw_response.graph(
+ version_id="",
+ account_id="account_id",
+ workflow_name="x",
+ )
From df4bf2083955dd860bd34b9e983c4e47fed52d11 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Jun 2026 22:49:09 +0000
Subject: [PATCH 15/18] chore(api): update composite API spec
---
.stats.yml | 4 ++--
src/cloudflare/types/intel/sinkhole.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 22230624e3d..cb8d9dc74c6 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 2325
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-eeab7a57d84f76af6bb611b6eb115f3ab818505d7f8d09c35751d4ba4bb0788e.yml
-openapi_spec_hash: 63308a10826084e5f64b82f361d8dcd2
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-b5699393100fa569c2545b6493296916461ed17473e2b20dd3526e86fd0f88b2.yml
+openapi_spec_hash: de2adf7ddb1e1aff63955c3adea743ab
config_hash: 3e02eec9f8dbb1c042399a4bea3363d2
diff --git a/src/cloudflare/types/intel/sinkhole.py b/src/cloudflare/types/intel/sinkhole.py
index 9895bde4bc0..8e404467050 100644
--- a/src/cloudflare/types/intel/sinkhole.py
+++ b/src/cloudflare/types/intel/sinkhole.py
@@ -9,7 +9,7 @@
class Sinkhole(BaseModel):
- id: Optional[int] = None
+ id: Optional[str] = None
"""The unique identifier for the sinkhole."""
account_tag: Optional[str] = None
From 2a9bfed496109bfe6172dbbb18e2128f69a60d6e Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 8 Jun 2026 08:55:25 +0000
Subject: [PATCH 16/18] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index cb8d9dc74c6..2c4e06e212e 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 2325
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-b5699393100fa569c2545b6493296916461ed17473e2b20dd3526e86fd0f88b2.yml
-openapi_spec_hash: de2adf7ddb1e1aff63955c3adea743ab
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-5a5a37c4938dd4a995c013e423aad8d0a4bdfc72448d39230352918aaa5a087a.yml
+openapi_spec_hash: 4fc7ac1b97d37c76f38db408ab2ed0cd
config_hash: 3e02eec9f8dbb1c042399a4bea3363d2
From b9d7b797d3a2ea59533d07d281143928a68a4687 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 8 Jun 2026 13:42:25 +0000
Subject: [PATCH 17/18] feat: feat(resource_sharing): add Terraform resource
definitions for shares
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix(resource_sharing): update share_resource method paths to match share-api rename
share-api MR !426 renamed the path placeholder on the share-resource
endpoints from {resource_id} to {share_resource_id} (the OpenAPI metadata
name only; URLs unchanged). The Stainless config's method-to-path
mapping still referenced the old name, so after the new spec flowed
through, Stainless couldn't resolve GET/PUT/DELETE on
/accounts/{account_id}/shares/{share_id}/resources/{...} and emitted
empty Read/Update/Delete handlers in the Terraform target. The Go SDK
target dropped those methods entirely.
Update both the production config and the testdata copy so the method
strings match the renamed path. Stainless's next codegen run will
restore the full CRUD surface for cloudflare_share_resource and the
corresponding methods in cloudflare-go's resource_sharing.Resources.
Ref: GRM-887
Related: cloudflare/grm/share-api!426 (the OpenAPI rename)
Cherry-pick of staging !911.
* fix(resource_sharing): drop bulk-replace recipients endpoint to fix codegen
* fix(resource_sharing): drop bulk-replace recipients endpoint to fix codegen
The Stainless model generator conflates the single-object create endpoint
(POST /shares/{share_id}/recipients) with the bulk-replace endpoint
(PUT /shares/{share_id}/recipients) when both are mapped under the same
subresource. The resulting model has a list-typed `body` field whose
MarshalJSON serializes the list as a JSON array on both create and
update — producing 400 errors on every single-recipient create.
Removing the bulk-replace mapping makes Stainless generate a model with
a single-object body, which the API accepts for create. Users wanting
to replace all recipients at once can issue successive create/delete
operations or hit the API directly.
Ref: GRM-887
* feat(resource_sharing): add Terraform resource definitions for shares
* feat(resource_sharing): add Terraform resource definitions for shares
Add terraform: blocks to resource_sharing and its subresources
(recipients, resources) so Stainless generates Terraform resources:
- cloudflare_share (full CRUD)
- cloudflare_share_recipient (create, read, delete, bulk-replace)
- cloudflare_share_resource (full CRUD)
Also adds the PUT /shares/{share_id}/recipients bulk-replace endpoint
to the recipients subresource.
Ref: GRM-887
---
.stats.yml | 6 +-
.../resources/resource_sharing/api.md | 11 +-
.../resources/resource_sharing/resources.py | 377 +++++++++++++++++-
.../types/resource_sharing/__init__.py | 4 +
.../resource_delete_response.py | 44 ++
.../resource_sharing/resource_get_response.py | 44 ++
.../resource_update_params.py | 18 +
.../resource_update_response.py | 44 ++
.../resource_sharing/test_resources.py | 375 +++++++++++++++++
9 files changed, 918 insertions(+), 5 deletions(-)
create mode 100644 src/cloudflare/types/resource_sharing/resource_delete_response.py
create mode 100644 src/cloudflare/types/resource_sharing/resource_get_response.py
create mode 100644 src/cloudflare/types/resource_sharing/resource_update_params.py
create mode 100644 src/cloudflare/types/resource_sharing/resource_update_response.py
diff --git a/.stats.yml b/.stats.yml
index 2c4e06e212e..fd377a29cab 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 2325
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-5a5a37c4938dd4a995c013e423aad8d0a4bdfc72448d39230352918aaa5a087a.yml
+configured_endpoints: 2328
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-8f20ff18bc6b099ac6a3bf68ff72f7c1ee76fe8d94ffdb868021bd760e756b2d.yml
openapi_spec_hash: 4fc7ac1b97d37c76f38db408ab2ed0cd
-config_hash: 3e02eec9f8dbb1c042399a4bea3363d2
+config_hash: 2a121dbc935ddd79ad2e7667bd9cfbcb
diff --git a/src/cloudflare/resources/resource_sharing/api.md b/src/cloudflare/resources/resource_sharing/api.md
index 7fec9dc27db..00b2d014706 100644
--- a/src/cloudflare/resources/resource_sharing/api.md
+++ b/src/cloudflare/resources/resource_sharing/api.md
@@ -45,10 +45,19 @@ Methods:
Types:
```python
-from cloudflare.types.resource_sharing import ResourceCreateResponse, ResourceListResponse
+from cloudflare.types.resource_sharing import (
+ ResourceCreateResponse,
+ ResourceUpdateResponse,
+ ResourceListResponse,
+ ResourceDeleteResponse,
+ ResourceGetResponse,
+)
```
Methods:
- client.resource_sharing.resources.create(share_id, \*, account_id, \*\*params) -> Optional[ResourceCreateResponse]
+- client.resource_sharing.resources.update(share_resource_id, \*, account_id, share_id, \*\*params) -> Optional[ResourceUpdateResponse]
- client.resource_sharing.resources.list(share_id, \*, account_id, \*\*params) -> SyncV4PagePaginationArray[ResourceListResponse]
+- client.resource_sharing.resources.delete(share_resource_id, \*, account_id, share_id) -> Optional[ResourceDeleteResponse]
+- client.resource_sharing.resources.get(share_resource_id, \*, account_id, share_id) -> Optional[ResourceGetResponse]
diff --git a/src/cloudflare/resources/resource_sharing/resources.py b/src/cloudflare/resources/resource_sharing/resources.py
index 84a136aabe7..1016063f44c 100644
--- a/src/cloudflare/resources/resource_sharing/resources.py
+++ b/src/cloudflare/resources/resource_sharing/resources.py
@@ -20,9 +20,12 @@
from ..._wrappers import ResultWrapper
from ...pagination import SyncV4PagePaginationArray, AsyncV4PagePaginationArray
from ..._base_client import AsyncPaginator, make_request_options
-from ...types.resource_sharing import resource_list_params, resource_create_params
+from ...types.resource_sharing import resource_list_params, resource_create_params, resource_update_params
+from ...types.resource_sharing.resource_get_response import ResourceGetResponse
from ...types.resource_sharing.resource_list_response import ResourceListResponse
from ...types.resource_sharing.resource_create_response import ResourceCreateResponse
+from ...types.resource_sharing.resource_delete_response import ResourceDeleteResponse
+from ...types.resource_sharing.resource_update_response import ResourceUpdateResponse
__all__ = ["ResourcesResource", "AsyncResourcesResource"]
@@ -121,6 +124,65 @@ def create(
cast_to=cast(Type[Optional[ResourceCreateResponse]], ResultWrapper[ResourceCreateResponse]),
)
+ def update(
+ self,
+ share_resource_id: str,
+ *,
+ account_id: str,
+ share_id: str,
+ meta: object,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[ResourceUpdateResponse]:
+ """
+ Update is not immediate, an updated share resource object with a new status will
+ be returned.
+
+ Args:
+ account_id: Account identifier.
+
+ share_id: Share identifier tag.
+
+ share_resource_id: Share Resource identifier.
+
+ meta: Resource Metadata.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not share_id:
+ raise ValueError(f"Expected a non-empty value for `share_id` but received {share_id!r}")
+ if not share_resource_id:
+ raise ValueError(f"Expected a non-empty value for `share_resource_id` but received {share_resource_id!r}")
+ return self._put(
+ path_template(
+ "/accounts/{account_id}/shares/{share_id}/resources/{share_resource_id}",
+ account_id=account_id,
+ share_id=share_id,
+ share_resource_id=share_resource_id,
+ ),
+ body=maybe_transform({"meta": meta}, resource_update_params.ResourceUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[ResourceUpdateResponse]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[ResourceUpdateResponse]], ResultWrapper[ResourceUpdateResponse]),
+ )
+
def list(
self,
share_id: str,
@@ -199,6 +261,115 @@ def list(
model=ResourceListResponse,
)
+ def delete(
+ self,
+ share_resource_id: str,
+ *,
+ account_id: str,
+ share_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[ResourceDeleteResponse]:
+ """
+ Deletion is not immediate, an updated share resource object with a new status
+ will be returned.
+
+ Args:
+ account_id: Account identifier.
+
+ share_id: Share identifier tag.
+
+ share_resource_id: Share Resource identifier.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not share_id:
+ raise ValueError(f"Expected a non-empty value for `share_id` but received {share_id!r}")
+ if not share_resource_id:
+ raise ValueError(f"Expected a non-empty value for `share_resource_id` but received {share_resource_id!r}")
+ return self._delete(
+ path_template(
+ "/accounts/{account_id}/shares/{share_id}/resources/{share_resource_id}",
+ account_id=account_id,
+ share_id=share_id,
+ share_resource_id=share_resource_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[ResourceDeleteResponse]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[ResourceDeleteResponse]], ResultWrapper[ResourceDeleteResponse]),
+ )
+
+ def get(
+ self,
+ share_resource_id: str,
+ *,
+ account_id: str,
+ share_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[ResourceGetResponse]:
+ """
+ Get share resource by ID.
+
+ Args:
+ account_id: Account identifier.
+
+ share_id: Share identifier tag.
+
+ share_resource_id: Share Resource identifier.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not share_id:
+ raise ValueError(f"Expected a non-empty value for `share_id` but received {share_id!r}")
+ if not share_resource_id:
+ raise ValueError(f"Expected a non-empty value for `share_resource_id` but received {share_resource_id!r}")
+ return self._get(
+ path_template(
+ "/accounts/{account_id}/shares/{share_id}/resources/{share_resource_id}",
+ account_id=account_id,
+ share_id=share_id,
+ share_resource_id=share_resource_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[ResourceGetResponse]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[ResourceGetResponse]], ResultWrapper[ResourceGetResponse]),
+ )
+
class AsyncResourcesResource(AsyncAPIResource):
@cached_property
@@ -294,6 +465,65 @@ async def create(
cast_to=cast(Type[Optional[ResourceCreateResponse]], ResultWrapper[ResourceCreateResponse]),
)
+ async def update(
+ self,
+ share_resource_id: str,
+ *,
+ account_id: str,
+ share_id: str,
+ meta: object,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[ResourceUpdateResponse]:
+ """
+ Update is not immediate, an updated share resource object with a new status will
+ be returned.
+
+ Args:
+ account_id: Account identifier.
+
+ share_id: Share identifier tag.
+
+ share_resource_id: Share Resource identifier.
+
+ meta: Resource Metadata.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not share_id:
+ raise ValueError(f"Expected a non-empty value for `share_id` but received {share_id!r}")
+ if not share_resource_id:
+ raise ValueError(f"Expected a non-empty value for `share_resource_id` but received {share_resource_id!r}")
+ return await self._put(
+ path_template(
+ "/accounts/{account_id}/shares/{share_id}/resources/{share_resource_id}",
+ account_id=account_id,
+ share_id=share_id,
+ share_resource_id=share_resource_id,
+ ),
+ body=await async_maybe_transform({"meta": meta}, resource_update_params.ResourceUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[ResourceUpdateResponse]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[ResourceUpdateResponse]], ResultWrapper[ResourceUpdateResponse]),
+ )
+
def list(
self,
share_id: str,
@@ -372,6 +602,115 @@ def list(
model=ResourceListResponse,
)
+ async def delete(
+ self,
+ share_resource_id: str,
+ *,
+ account_id: str,
+ share_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[ResourceDeleteResponse]:
+ """
+ Deletion is not immediate, an updated share resource object with a new status
+ will be returned.
+
+ Args:
+ account_id: Account identifier.
+
+ share_id: Share identifier tag.
+
+ share_resource_id: Share Resource identifier.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not share_id:
+ raise ValueError(f"Expected a non-empty value for `share_id` but received {share_id!r}")
+ if not share_resource_id:
+ raise ValueError(f"Expected a non-empty value for `share_resource_id` but received {share_resource_id!r}")
+ return await self._delete(
+ path_template(
+ "/accounts/{account_id}/shares/{share_id}/resources/{share_resource_id}",
+ account_id=account_id,
+ share_id=share_id,
+ share_resource_id=share_resource_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[ResourceDeleteResponse]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[ResourceDeleteResponse]], ResultWrapper[ResourceDeleteResponse]),
+ )
+
+ async def get(
+ self,
+ share_resource_id: str,
+ *,
+ account_id: str,
+ share_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Optional[ResourceGetResponse]:
+ """
+ Get share resource by ID.
+
+ Args:
+ account_id: Account identifier.
+
+ share_id: Share identifier tag.
+
+ share_resource_id: Share Resource identifier.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not share_id:
+ raise ValueError(f"Expected a non-empty value for `share_id` but received {share_id!r}")
+ if not share_resource_id:
+ raise ValueError(f"Expected a non-empty value for `share_resource_id` but received {share_resource_id!r}")
+ return await self._get(
+ path_template(
+ "/accounts/{account_id}/shares/{share_id}/resources/{share_resource_id}",
+ account_id=account_id,
+ share_id=share_id,
+ share_resource_id=share_resource_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=ResultWrapper[Optional[ResourceGetResponse]]._unwrapper,
+ ),
+ cast_to=cast(Type[Optional[ResourceGetResponse]], ResultWrapper[ResourceGetResponse]),
+ )
+
class ResourcesResourceWithRawResponse:
def __init__(self, resources: ResourcesResource) -> None:
@@ -380,9 +719,18 @@ def __init__(self, resources: ResourcesResource) -> None:
self.create = to_raw_response_wrapper(
resources.create,
)
+ self.update = to_raw_response_wrapper(
+ resources.update,
+ )
self.list = to_raw_response_wrapper(
resources.list,
)
+ self.delete = to_raw_response_wrapper(
+ resources.delete,
+ )
+ self.get = to_raw_response_wrapper(
+ resources.get,
+ )
class AsyncResourcesResourceWithRawResponse:
@@ -392,9 +740,18 @@ def __init__(self, resources: AsyncResourcesResource) -> None:
self.create = async_to_raw_response_wrapper(
resources.create,
)
+ self.update = async_to_raw_response_wrapper(
+ resources.update,
+ )
self.list = async_to_raw_response_wrapper(
resources.list,
)
+ self.delete = async_to_raw_response_wrapper(
+ resources.delete,
+ )
+ self.get = async_to_raw_response_wrapper(
+ resources.get,
+ )
class ResourcesResourceWithStreamingResponse:
@@ -404,9 +761,18 @@ def __init__(self, resources: ResourcesResource) -> None:
self.create = to_streamed_response_wrapper(
resources.create,
)
+ self.update = to_streamed_response_wrapper(
+ resources.update,
+ )
self.list = to_streamed_response_wrapper(
resources.list,
)
+ self.delete = to_streamed_response_wrapper(
+ resources.delete,
+ )
+ self.get = to_streamed_response_wrapper(
+ resources.get,
+ )
class AsyncResourcesResourceWithStreamingResponse:
@@ -416,6 +782,15 @@ def __init__(self, resources: AsyncResourcesResource) -> None:
self.create = async_to_streamed_response_wrapper(
resources.create,
)
+ self.update = async_to_streamed_response_wrapper(
+ resources.update,
+ )
self.list = async_to_streamed_response_wrapper(
resources.list,
)
+ self.delete = async_to_streamed_response_wrapper(
+ resources.delete,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ resources.get,
+ )
diff --git a/src/cloudflare/types/resource_sharing/__init__.py b/src/cloudflare/types/resource_sharing/__init__.py
index c38cd309a1e..7655008fc78 100644
--- a/src/cloudflare/types/resource_sharing/__init__.py
+++ b/src/cloudflare/types/resource_sharing/__init__.py
@@ -5,12 +5,16 @@
from .recipient_get_params import RecipientGetParams as RecipientGetParams
from .resource_list_params import ResourceListParams as ResourceListParams
from .recipient_list_params import RecipientListParams as RecipientListParams
+from .resource_get_response import ResourceGetResponse as ResourceGetResponse
from .recipient_get_response import RecipientGetResponse as RecipientGetResponse
from .resource_create_params import ResourceCreateParams as ResourceCreateParams
from .resource_list_response import ResourceListResponse as ResourceListResponse
+from .resource_update_params import ResourceUpdateParams as ResourceUpdateParams
from .recipient_create_params import RecipientCreateParams as RecipientCreateParams
from .recipient_list_response import RecipientListResponse as RecipientListResponse
from .resource_create_response import ResourceCreateResponse as ResourceCreateResponse
+from .resource_delete_response import ResourceDeleteResponse as ResourceDeleteResponse
+from .resource_update_response import ResourceUpdateResponse as ResourceUpdateResponse
from .recipient_create_response import RecipientCreateResponse as RecipientCreateResponse
from .recipient_delete_response import RecipientDeleteResponse as RecipientDeleteResponse
from .resource_sharing_get_params import ResourceSharingGetParams as ResourceSharingGetParams
diff --git a/src/cloudflare/types/resource_sharing/resource_delete_response.py b/src/cloudflare/types/resource_sharing/resource_delete_response.py
new file mode 100644
index 00000000000..deff33152bc
--- /dev/null
+++ b/src/cloudflare/types/resource_sharing/resource_delete_response.py
@@ -0,0 +1,44 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["ResourceDeleteResponse"]
+
+
+class ResourceDeleteResponse(BaseModel):
+ id: str
+ """Share Resource identifier."""
+
+ created: datetime
+ """When the share was created."""
+
+ meta: object
+ """Resource Metadata."""
+
+ modified: datetime
+ """When the share was modified."""
+
+ resource_account_id: str
+ """Account identifier."""
+
+ resource_id: str
+ """Share Resource identifier."""
+
+ resource_type: Literal[
+ "custom-ruleset",
+ "gateway-policy",
+ "gateway-destination-ip",
+ "gateway-block-page-settings",
+ "gateway-extended-email-matching",
+ "idp-federation-grant",
+ ]
+ """Resource Type."""
+
+ resource_version: int
+ """Resource Version."""
+
+ status: Literal["active", "deleting", "deleted"]
+ """Resource Status."""
diff --git a/src/cloudflare/types/resource_sharing/resource_get_response.py b/src/cloudflare/types/resource_sharing/resource_get_response.py
new file mode 100644
index 00000000000..a357bb5074f
--- /dev/null
+++ b/src/cloudflare/types/resource_sharing/resource_get_response.py
@@ -0,0 +1,44 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["ResourceGetResponse"]
+
+
+class ResourceGetResponse(BaseModel):
+ id: str
+ """Share Resource identifier."""
+
+ created: datetime
+ """When the share was created."""
+
+ meta: object
+ """Resource Metadata."""
+
+ modified: datetime
+ """When the share was modified."""
+
+ resource_account_id: str
+ """Account identifier."""
+
+ resource_id: str
+ """Share Resource identifier."""
+
+ resource_type: Literal[
+ "custom-ruleset",
+ "gateway-policy",
+ "gateway-destination-ip",
+ "gateway-block-page-settings",
+ "gateway-extended-email-matching",
+ "idp-federation-grant",
+ ]
+ """Resource Type."""
+
+ resource_version: int
+ """Resource Version."""
+
+ status: Literal["active", "deleting", "deleted"]
+ """Resource Status."""
diff --git a/src/cloudflare/types/resource_sharing/resource_update_params.py b/src/cloudflare/types/resource_sharing/resource_update_params.py
new file mode 100644
index 00000000000..d69bbdc0eb0
--- /dev/null
+++ b/src/cloudflare/types/resource_sharing/resource_update_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["ResourceUpdateParams"]
+
+
+class ResourceUpdateParams(TypedDict, total=False):
+ account_id: Required[str]
+ """Account identifier."""
+
+ share_id: Required[str]
+ """Share identifier tag."""
+
+ meta: Required[object]
+ """Resource Metadata."""
diff --git a/src/cloudflare/types/resource_sharing/resource_update_response.py b/src/cloudflare/types/resource_sharing/resource_update_response.py
new file mode 100644
index 00000000000..f207220bf2d
--- /dev/null
+++ b/src/cloudflare/types/resource_sharing/resource_update_response.py
@@ -0,0 +1,44 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["ResourceUpdateResponse"]
+
+
+class ResourceUpdateResponse(BaseModel):
+ id: str
+ """Share Resource identifier."""
+
+ created: datetime
+ """When the share was created."""
+
+ meta: object
+ """Resource Metadata."""
+
+ modified: datetime
+ """When the share was modified."""
+
+ resource_account_id: str
+ """Account identifier."""
+
+ resource_id: str
+ """Share Resource identifier."""
+
+ resource_type: Literal[
+ "custom-ruleset",
+ "gateway-policy",
+ "gateway-destination-ip",
+ "gateway-block-page-settings",
+ "gateway-extended-email-matching",
+ "idp-federation-grant",
+ ]
+ """Resource Type."""
+
+ resource_version: int
+ """Resource Version."""
+
+ status: Literal["active", "deleting", "deleted"]
+ """Resource Status."""
diff --git a/tests/api_resources/resource_sharing/test_resources.py b/tests/api_resources/resource_sharing/test_resources.py
index ccb14582074..a07aeebdca1 100644
--- a/tests/api_resources/resource_sharing/test_resources.py
+++ b/tests/api_resources/resource_sharing/test_resources.py
@@ -11,8 +11,11 @@
from tests.utils import assert_matches_type
from cloudflare.pagination import SyncV4PagePaginationArray, AsyncV4PagePaginationArray
from cloudflare.types.resource_sharing import (
+ ResourceGetResponse,
ResourceListResponse,
ResourceCreateResponse,
+ ResourceDeleteResponse,
+ ResourceUpdateResponse,
)
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -89,6 +92,72 @@ def test_path_params_create(self, client: Cloudflare) -> None:
resource_type="custom-ruleset",
)
+ @parametrize
+ def test_method_update(self, client: Cloudflare) -> None:
+ resource = client.resource_sharing.resources.update(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ meta={},
+ )
+ assert_matches_type(Optional[ResourceUpdateResponse], resource, path=["response"])
+
+ @parametrize
+ def test_raw_response_update(self, client: Cloudflare) -> None:
+ response = client.resource_sharing.resources.with_raw_response.update(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ meta={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ resource = response.parse()
+ assert_matches_type(Optional[ResourceUpdateResponse], resource, path=["response"])
+
+ @parametrize
+ def test_streaming_response_update(self, client: Cloudflare) -> None:
+ with client.resource_sharing.resources.with_streaming_response.update(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ meta={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ resource = response.parse()
+ assert_matches_type(Optional[ResourceUpdateResponse], resource, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_update(self, client: Cloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ client.resource_sharing.resources.with_raw_response.update(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ meta={},
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `share_id` but received ''"):
+ client.resource_sharing.resources.with_raw_response.update(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="",
+ meta={},
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `share_resource_id` but received ''"):
+ client.resource_sharing.resources.with_raw_response.update(
+ share_resource_id="",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ meta={},
+ )
+
@parametrize
def test_method_list(self, client: Cloudflare) -> None:
resource = client.resource_sharing.resources.list(
@@ -149,6 +218,126 @@ def test_path_params_list(self, client: Cloudflare) -> None:
account_id="023e105f4ecef8ad9ca31a8372d0c353",
)
+ @parametrize
+ def test_method_delete(self, client: Cloudflare) -> None:
+ resource = client.resource_sharing.resources.delete(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+ assert_matches_type(Optional[ResourceDeleteResponse], resource, path=["response"])
+
+ @parametrize
+ def test_raw_response_delete(self, client: Cloudflare) -> None:
+ response = client.resource_sharing.resources.with_raw_response.delete(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ resource = response.parse()
+ assert_matches_type(Optional[ResourceDeleteResponse], resource, path=["response"])
+
+ @parametrize
+ def test_streaming_response_delete(self, client: Cloudflare) -> None:
+ with client.resource_sharing.resources.with_streaming_response.delete(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ resource = response.parse()
+ assert_matches_type(Optional[ResourceDeleteResponse], resource, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_delete(self, client: Cloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ client.resource_sharing.resources.with_raw_response.delete(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `share_id` but received ''"):
+ client.resource_sharing.resources.with_raw_response.delete(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `share_resource_id` but received ''"):
+ client.resource_sharing.resources.with_raw_response.delete(
+ share_resource_id="",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+
+ @parametrize
+ def test_method_get(self, client: Cloudflare) -> None:
+ resource = client.resource_sharing.resources.get(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+ assert_matches_type(Optional[ResourceGetResponse], resource, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Cloudflare) -> None:
+ response = client.resource_sharing.resources.with_raw_response.get(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ resource = response.parse()
+ assert_matches_type(Optional[ResourceGetResponse], resource, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Cloudflare) -> None:
+ with client.resource_sharing.resources.with_streaming_response.get(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ resource = response.parse()
+ assert_matches_type(Optional[ResourceGetResponse], resource, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Cloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ client.resource_sharing.resources.with_raw_response.get(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `share_id` but received ''"):
+ client.resource_sharing.resources.with_raw_response.get(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `share_resource_id` but received ''"):
+ client.resource_sharing.resources.with_raw_response.get(
+ share_resource_id="",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+
class TestAsyncResources:
parametrize = pytest.mark.parametrize(
@@ -223,6 +412,72 @@ async def test_path_params_create(self, async_client: AsyncCloudflare) -> None:
resource_type="custom-ruleset",
)
+ @parametrize
+ async def test_method_update(self, async_client: AsyncCloudflare) -> None:
+ resource = await async_client.resource_sharing.resources.update(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ meta={},
+ )
+ assert_matches_type(Optional[ResourceUpdateResponse], resource, path=["response"])
+
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncCloudflare) -> None:
+ response = await async_client.resource_sharing.resources.with_raw_response.update(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ meta={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ resource = await response.parse()
+ assert_matches_type(Optional[ResourceUpdateResponse], resource, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncCloudflare) -> None:
+ async with async_client.resource_sharing.resources.with_streaming_response.update(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ meta={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ resource = await response.parse()
+ assert_matches_type(Optional[ResourceUpdateResponse], resource, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncCloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ await async_client.resource_sharing.resources.with_raw_response.update(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ meta={},
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `share_id` but received ''"):
+ await async_client.resource_sharing.resources.with_raw_response.update(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="",
+ meta={},
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `share_resource_id` but received ''"):
+ await async_client.resource_sharing.resources.with_raw_response.update(
+ share_resource_id="",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ meta={},
+ )
+
@parametrize
async def test_method_list(self, async_client: AsyncCloudflare) -> None:
resource = await async_client.resource_sharing.resources.list(
@@ -282,3 +537,123 @@ async def test_path_params_list(self, async_client: AsyncCloudflare) -> None:
share_id="",
account_id="023e105f4ecef8ad9ca31a8372d0c353",
)
+
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncCloudflare) -> None:
+ resource = await async_client.resource_sharing.resources.delete(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+ assert_matches_type(Optional[ResourceDeleteResponse], resource, path=["response"])
+
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncCloudflare) -> None:
+ response = await async_client.resource_sharing.resources.with_raw_response.delete(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ resource = await response.parse()
+ assert_matches_type(Optional[ResourceDeleteResponse], resource, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncCloudflare) -> None:
+ async with async_client.resource_sharing.resources.with_streaming_response.delete(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ resource = await response.parse()
+ assert_matches_type(Optional[ResourceDeleteResponse], resource, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncCloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ await async_client.resource_sharing.resources.with_raw_response.delete(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `share_id` but received ''"):
+ await async_client.resource_sharing.resources.with_raw_response.delete(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `share_resource_id` but received ''"):
+ await async_client.resource_sharing.resources.with_raw_response.delete(
+ share_resource_id="",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncCloudflare) -> None:
+ resource = await async_client.resource_sharing.resources.get(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+ assert_matches_type(Optional[ResourceGetResponse], resource, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncCloudflare) -> None:
+ response = await async_client.resource_sharing.resources.with_raw_response.get(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ resource = await response.parse()
+ assert_matches_type(Optional[ResourceGetResponse], resource, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncCloudflare) -> None:
+ async with async_client.resource_sharing.resources.with_streaming_response.get(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ resource = await response.parse()
+ assert_matches_type(Optional[ResourceGetResponse], resource, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncCloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ await async_client.resource_sharing.resources.with_raw_response.get(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `share_id` but received ''"):
+ await async_client.resource_sharing.resources.with_raw_response.get(
+ share_resource_id="023e105f4ecef8ad9ca31a8372d0c353",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `share_resource_id` but received ''"):
+ await async_client.resource_sharing.resources.with_raw_response.get(
+ share_resource_id="",
+ account_id="023e105f4ecef8ad9ca31a8372d0c353",
+ share_id="3fd85f74b32742f1bff64a85009dda07",
+ )
From fb51578010e79acf564b50d805d637541815f79a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 8 Jun 2026 16:08:02 +0000
Subject: [PATCH 18/18] feat: feat(dex): add device ISPs endpoint mapping
* feat(dex): add device ISPs endpoint mapping
* feat(dex): move isps to DEX > devices > isps list resource
* feat(dex): add device ISPs endpoint mapping
Map GET /accounts/{account_id}/dex/devices/{device_id}/isps to
zero_trust.dex.device_isps.list for SDK generation.
Ref: APIX-630
---
.stats.yml | 6 +-
src/cloudflare/resources/zero_trust/api.md | 14 +
.../resources/zero_trust/dex/__init__.py | 14 +
.../zero_trust/dex/devices/__init__.py | 33 +++
.../zero_trust/dex/devices/devices.py | 102 +++++++
.../resources/zero_trust/dex/devices/isps.py | 258 ++++++++++++++++++
.../resources/zero_trust/dex/dex.py | 32 +++
.../types/zero_trust/dex/devices/__init__.py | 6 +
.../zero_trust/dex/devices/isp_list_params.py | 36 +++
.../types/zero_trust/dex/devices/isps.py | 86 ++++++
.../zero_trust/dex/devices/__init__.py | 1 +
.../zero_trust/dex/devices/test_isps.py | 162 +++++++++++
12 files changed, 747 insertions(+), 3 deletions(-)
create mode 100644 src/cloudflare/resources/zero_trust/dex/devices/__init__.py
create mode 100644 src/cloudflare/resources/zero_trust/dex/devices/devices.py
create mode 100644 src/cloudflare/resources/zero_trust/dex/devices/isps.py
create mode 100644 src/cloudflare/types/zero_trust/dex/devices/__init__.py
create mode 100644 src/cloudflare/types/zero_trust/dex/devices/isp_list_params.py
create mode 100644 src/cloudflare/types/zero_trust/dex/devices/isps.py
create mode 100644 tests/api_resources/zero_trust/dex/devices/__init__.py
create mode 100644 tests/api_resources/zero_trust/dex/devices/test_isps.py
diff --git a/.stats.yml b/.stats.yml
index fd377a29cab..41b5b15dd67 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 2328
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-8f20ff18bc6b099ac6a3bf68ff72f7c1ee76fe8d94ffdb868021bd760e756b2d.yml
+configured_endpoints: 2329
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-884b4b7d2d826afd303115f717021363d35e76d0819f4822e9faf6c577ea66ca.yml
openapi_spec_hash: 4fc7ac1b97d37c76f38db408ab2ed0cd
-config_hash: 2a121dbc935ddd79ad2e7667bd9cfbcb
+config_hash: 1fe0cc702fafe448e633d379a5633326
diff --git a/src/cloudflare/resources/zero_trust/api.md b/src/cloudflare/resources/zero_trust/api.md
index 6cdbf208ea3..14f6d3f5e57 100644
--- a/src/cloudflare/resources/zero_trust/api.md
+++ b/src/cloudflare/resources/zero_trust/api.md
@@ -1192,6 +1192,20 @@ Methods:
- client.zero_trust.dex.rules.delete(rule_id, \*, account_id) -> Optional[RuleDeleteResponse]
- client.zero_trust.dex.rules.get(rule_id, \*, account_id) -> Optional[RuleGetResponse]
+### Devices
+
+#### ISPs
+
+Types:
+
+```python
+from cloudflare.types.zero_trust.dex.devices import ISPs
+```
+
+Methods:
+
+- client.zero_trust.dex.devices.isps.list(device_id, \*, account_id, \*\*params) -> SyncV4PagePagination[Optional[ISPs]]
+
## Tunnels
Types:
diff --git a/src/cloudflare/resources/zero_trust/dex/__init__.py b/src/cloudflare/resources/zero_trust/dex/__init__.py
index b4e5b64def8..076ef18e863 100644
--- a/src/cloudflare/resources/zero_trust/dex/__init__.py
+++ b/src/cloudflare/resources/zero_trust/dex/__init__.py
@@ -32,6 +32,14 @@
TestsResourceWithStreamingResponse,
AsyncTestsResourceWithStreamingResponse,
)
+from .devices import (
+ DevicesResource,
+ AsyncDevicesResource,
+ DevicesResourceWithRawResponse,
+ AsyncDevicesResourceWithRawResponse,
+ DevicesResourceWithStreamingResponse,
+ AsyncDevicesResourceWithStreamingResponse,
+)
from .commands import (
CommandsResource,
AsyncCommandsResource,
@@ -136,6 +144,12 @@
"AsyncRulesResourceWithRawResponse",
"RulesResourceWithStreamingResponse",
"AsyncRulesResourceWithStreamingResponse",
+ "DevicesResource",
+ "AsyncDevicesResource",
+ "DevicesResourceWithRawResponse",
+ "AsyncDevicesResourceWithRawResponse",
+ "DevicesResourceWithStreamingResponse",
+ "AsyncDevicesResourceWithStreamingResponse",
"DEXResource",
"AsyncDEXResource",
"DEXResourceWithRawResponse",
diff --git a/src/cloudflare/resources/zero_trust/dex/devices/__init__.py b/src/cloudflare/resources/zero_trust/dex/devices/__init__.py
new file mode 100644
index 00000000000..c38aac84a8b
--- /dev/null
+++ b/src/cloudflare/resources/zero_trust/dex/devices/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .isps import (
+ ISPsResource,
+ AsyncISPsResource,
+ ISPsResourceWithRawResponse,
+ AsyncISPsResourceWithRawResponse,
+ ISPsResourceWithStreamingResponse,
+ AsyncISPsResourceWithStreamingResponse,
+)
+from .devices import (
+ DevicesResource,
+ AsyncDevicesResource,
+ DevicesResourceWithRawResponse,
+ AsyncDevicesResourceWithRawResponse,
+ DevicesResourceWithStreamingResponse,
+ AsyncDevicesResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "ISPsResource",
+ "AsyncISPsResource",
+ "ISPsResourceWithRawResponse",
+ "AsyncISPsResourceWithRawResponse",
+ "ISPsResourceWithStreamingResponse",
+ "AsyncISPsResourceWithStreamingResponse",
+ "DevicesResource",
+ "AsyncDevicesResource",
+ "DevicesResourceWithRawResponse",
+ "AsyncDevicesResourceWithRawResponse",
+ "DevicesResourceWithStreamingResponse",
+ "AsyncDevicesResourceWithStreamingResponse",
+]
diff --git a/src/cloudflare/resources/zero_trust/dex/devices/devices.py b/src/cloudflare/resources/zero_trust/dex/devices/devices.py
new file mode 100644
index 00000000000..6af9a2a3313
--- /dev/null
+++ b/src/cloudflare/resources/zero_trust/dex/devices/devices.py
@@ -0,0 +1,102 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .isps import (
+ ISPsResource,
+ AsyncISPsResource,
+ ISPsResourceWithRawResponse,
+ AsyncISPsResourceWithRawResponse,
+ ISPsResourceWithStreamingResponse,
+ AsyncISPsResourceWithStreamingResponse,
+)
+from ....._compat import cached_property
+from ....._resource import SyncAPIResource, AsyncAPIResource
+
+__all__ = ["DevicesResource", "AsyncDevicesResource"]
+
+
+class DevicesResource(SyncAPIResource):
+ @cached_property
+ def isps(self) -> ISPsResource:
+ return ISPsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> DevicesResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#accessing-raw-response-data-eg-headers
+ """
+ return DevicesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> DevicesResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#with_streaming_response
+ """
+ return DevicesResourceWithStreamingResponse(self)
+
+
+class AsyncDevicesResource(AsyncAPIResource):
+ @cached_property
+ def isps(self) -> AsyncISPsResource:
+ return AsyncISPsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncDevicesResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncDevicesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncDevicesResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#with_streaming_response
+ """
+ return AsyncDevicesResourceWithStreamingResponse(self)
+
+
+class DevicesResourceWithRawResponse:
+ def __init__(self, devices: DevicesResource) -> None:
+ self._devices = devices
+
+ @cached_property
+ def isps(self) -> ISPsResourceWithRawResponse:
+ return ISPsResourceWithRawResponse(self._devices.isps)
+
+
+class AsyncDevicesResourceWithRawResponse:
+ def __init__(self, devices: AsyncDevicesResource) -> None:
+ self._devices = devices
+
+ @cached_property
+ def isps(self) -> AsyncISPsResourceWithRawResponse:
+ return AsyncISPsResourceWithRawResponse(self._devices.isps)
+
+
+class DevicesResourceWithStreamingResponse:
+ def __init__(self, devices: DevicesResource) -> None:
+ self._devices = devices
+
+ @cached_property
+ def isps(self) -> ISPsResourceWithStreamingResponse:
+ return ISPsResourceWithStreamingResponse(self._devices.isps)
+
+
+class AsyncDevicesResourceWithStreamingResponse:
+ def __init__(self, devices: AsyncDevicesResource) -> None:
+ self._devices = devices
+
+ @cached_property
+ def isps(self) -> AsyncISPsResourceWithStreamingResponse:
+ return AsyncISPsResourceWithStreamingResponse(self._devices.isps)
diff --git a/src/cloudflare/resources/zero_trust/dex/devices/isps.py b/src/cloudflare/resources/zero_trust/dex/devices/isps.py
new file mode 100644
index 00000000000..4e3675de167
--- /dev/null
+++ b/src/cloudflare/resources/zero_trust/dex/devices/isps.py
@@ -0,0 +1,258 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+import httpx
+
+from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ....._utils import path_template, maybe_transform
+from ....._compat import cached_property
+from ....._resource import SyncAPIResource, AsyncAPIResource
+from ....._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from .....pagination import SyncV4PagePagination, AsyncV4PagePagination
+from ....._base_client import AsyncPaginator, make_request_options
+from .....types.zero_trust.dex.devices import isp_list_params
+from .....types.zero_trust.dex.devices.isps import ISPs
+
+__all__ = ["ISPsResource", "AsyncISPsResource"]
+
+
+class ISPsResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> ISPsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#accessing-raw-response-data-eg-headers
+ """
+ return ISPsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ISPsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#with_streaming_response
+ """
+ return ISPsResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ device_id: str,
+ *,
+ account_id: str,
+ per_page: int,
+ cursor: str | Omit = omit,
+ from_: Union[str, datetime] | Omit = omit,
+ page: int | Omit = omit,
+ sort_by: Literal["time_start"] | Omit = omit,
+ sort_order: Literal["ASC", "DESC"] | Omit = omit,
+ to: Union[str, datetime] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SyncV4PagePagination[Optional[ISPs]]:
+ """
+ List ISP information observed for a specific device during traceroute tests.
+
+ Args:
+ device_id: API Resource UUID tag.
+
+ per_page: Number of items per page
+
+ cursor: Cursor for cursor-based pagination. Mutually exclusive with page.
+
+ from_: Start time for the query in ISO 8601 format
+
+ page: Page number of paginated results. Mutually exclusive with cursor.
+
+ sort_by: The field to sort results by
+
+ sort_order: The order to sort results
+
+ to: End time for the query in ISO 8601 format
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not device_id:
+ raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}")
+ return self._get_api_list(
+ path_template(
+ "/accounts/{account_id}/dex/devices/{device_id}/isps", account_id=account_id, device_id=device_id
+ ),
+ page=SyncV4PagePagination[Optional[ISPs]],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "per_page": per_page,
+ "cursor": cursor,
+ "from_": from_,
+ "page": page,
+ "sort_by": sort_by,
+ "sort_order": sort_order,
+ "to": to,
+ },
+ isp_list_params.ISPListParams,
+ ),
+ ),
+ model=ISPs,
+ )
+
+
+class AsyncISPsResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncISPsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncISPsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncISPsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/cloudflare/cloudflare-python#with_streaming_response
+ """
+ return AsyncISPsResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ device_id: str,
+ *,
+ account_id: str,
+ per_page: int,
+ cursor: str | Omit = omit,
+ from_: Union[str, datetime] | Omit = omit,
+ page: int | Omit = omit,
+ sort_by: Literal["time_start"] | Omit = omit,
+ sort_order: Literal["ASC", "DESC"] | Omit = omit,
+ to: Union[str, datetime] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncPaginator[Optional[ISPs], AsyncV4PagePagination[Optional[ISPs]]]:
+ """
+ List ISP information observed for a specific device during traceroute tests.
+
+ Args:
+ device_id: API Resource UUID tag.
+
+ per_page: Number of items per page
+
+ cursor: Cursor for cursor-based pagination. Mutually exclusive with page.
+
+ from_: Start time for the query in ISO 8601 format
+
+ page: Page number of paginated results. Mutually exclusive with cursor.
+
+ sort_by: The field to sort results by
+
+ sort_order: The order to sort results
+
+ to: End time for the query in ISO 8601 format
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ if not device_id:
+ raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}")
+ return self._get_api_list(
+ path_template(
+ "/accounts/{account_id}/dex/devices/{device_id}/isps", account_id=account_id, device_id=device_id
+ ),
+ page=AsyncV4PagePagination[Optional[ISPs]],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "per_page": per_page,
+ "cursor": cursor,
+ "from_": from_,
+ "page": page,
+ "sort_by": sort_by,
+ "sort_order": sort_order,
+ "to": to,
+ },
+ isp_list_params.ISPListParams,
+ ),
+ ),
+ model=ISPs,
+ )
+
+
+class ISPsResourceWithRawResponse:
+ def __init__(self, isps: ISPsResource) -> None:
+ self._isps = isps
+
+ self.list = to_raw_response_wrapper(
+ isps.list,
+ )
+
+
+class AsyncISPsResourceWithRawResponse:
+ def __init__(self, isps: AsyncISPsResource) -> None:
+ self._isps = isps
+
+ self.list = async_to_raw_response_wrapper(
+ isps.list,
+ )
+
+
+class ISPsResourceWithStreamingResponse:
+ def __init__(self, isps: ISPsResource) -> None:
+ self._isps = isps
+
+ self.list = to_streamed_response_wrapper(
+ isps.list,
+ )
+
+
+class AsyncISPsResourceWithStreamingResponse:
+ def __init__(self, isps: AsyncISPsResource) -> None:
+ self._isps = isps
+
+ self.list = async_to_streamed_response_wrapper(
+ isps.list,
+ )
diff --git a/src/cloudflare/resources/zero_trust/dex/dex.py b/src/cloudflare/resources/zero_trust/dex/dex.py
index 82bd1271b6f..7239e291764 100644
--- a/src/cloudflare/resources/zero_trust/dex/dex.py
+++ b/src/cloudflare/resources/zero_trust/dex/dex.py
@@ -28,6 +28,14 @@
AsyncTestsResourceWithStreamingResponse,
)
from ...._resource import SyncAPIResource, AsyncAPIResource
+from .devices.devices import (
+ DevicesResource,
+ AsyncDevicesResource,
+ DevicesResourceWithRawResponse,
+ AsyncDevicesResourceWithRawResponse,
+ DevicesResourceWithStreamingResponse,
+ AsyncDevicesResourceWithStreamingResponse,
+)
from .traceroute_tests import (
TracerouteTestsResource,
AsyncTracerouteTestsResource,
@@ -117,6 +125,10 @@ def traceroute_tests(self) -> TracerouteTestsResource:
def rules(self) -> RulesResource:
return RulesResource(self._client)
+ @cached_property
+ def devices(self) -> DevicesResource:
+ return DevicesResource(self._client)
+
@cached_property
def with_raw_response(self) -> DEXResourceWithRawResponse:
"""
@@ -174,6 +186,10 @@ def traceroute_tests(self) -> AsyncTracerouteTestsResource:
def rules(self) -> AsyncRulesResource:
return AsyncRulesResource(self._client)
+ @cached_property
+ def devices(self) -> AsyncDevicesResource:
+ return AsyncDevicesResource(self._client)
+
@cached_property
def with_raw_response(self) -> AsyncDEXResourceWithRawResponse:
"""
@@ -234,6 +250,10 @@ def traceroute_tests(self) -> TracerouteTestsResourceWithRawResponse:
def rules(self) -> RulesResourceWithRawResponse:
return RulesResourceWithRawResponse(self._dex.rules)
+ @cached_property
+ def devices(self) -> DevicesResourceWithRawResponse:
+ return DevicesResourceWithRawResponse(self._dex.devices)
+
class AsyncDEXResourceWithRawResponse:
def __init__(self, dex: AsyncDEXResource) -> None:
@@ -275,6 +295,10 @@ def traceroute_tests(self) -> AsyncTracerouteTestsResourceWithRawResponse:
def rules(self) -> AsyncRulesResourceWithRawResponse:
return AsyncRulesResourceWithRawResponse(self._dex.rules)
+ @cached_property
+ def devices(self) -> AsyncDevicesResourceWithRawResponse:
+ return AsyncDevicesResourceWithRawResponse(self._dex.devices)
+
class DEXResourceWithStreamingResponse:
def __init__(self, dex: DEXResource) -> None:
@@ -316,6 +340,10 @@ def traceroute_tests(self) -> TracerouteTestsResourceWithStreamingResponse:
def rules(self) -> RulesResourceWithStreamingResponse:
return RulesResourceWithStreamingResponse(self._dex.rules)
+ @cached_property
+ def devices(self) -> DevicesResourceWithStreamingResponse:
+ return DevicesResourceWithStreamingResponse(self._dex.devices)
+
class AsyncDEXResourceWithStreamingResponse:
def __init__(self, dex: AsyncDEXResource) -> None:
@@ -356,3 +384,7 @@ def traceroute_tests(self) -> AsyncTracerouteTestsResourceWithStreamingResponse:
@cached_property
def rules(self) -> AsyncRulesResourceWithStreamingResponse:
return AsyncRulesResourceWithStreamingResponse(self._dex.rules)
+
+ @cached_property
+ def devices(self) -> AsyncDevicesResourceWithStreamingResponse:
+ return AsyncDevicesResourceWithStreamingResponse(self._dex.devices)
diff --git a/src/cloudflare/types/zero_trust/dex/devices/__init__.py b/src/cloudflare/types/zero_trust/dex/devices/__init__.py
new file mode 100644
index 00000000000..a923e772912
--- /dev/null
+++ b/src/cloudflare/types/zero_trust/dex/devices/__init__.py
@@ -0,0 +1,6 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .isps import ISPs as ISPs
+from .isp_list_params import ISPListParams as ISPListParams
diff --git a/src/cloudflare/types/zero_trust/dex/devices/isp_list_params.py b/src/cloudflare/types/zero_trust/dex/devices/isp_list_params.py
new file mode 100644
index 00000000000..127afcc2b87
--- /dev/null
+++ b/src/cloudflare/types/zero_trust/dex/devices/isp_list_params.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from datetime import datetime
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from ....._utils import PropertyInfo
+
+__all__ = ["ISPListParams"]
+
+
+class ISPListParams(TypedDict, total=False):
+ account_id: Required[str]
+
+ per_page: Required[int]
+ """Number of items per page"""
+
+ cursor: str
+ """Cursor for cursor-based pagination. Mutually exclusive with page."""
+
+ from_: Annotated[Union[str, datetime], PropertyInfo(alias="from", format="iso8601")]
+ """Start time for the query in ISO 8601 format"""
+
+ page: int
+ """Page number of paginated results. Mutually exclusive with cursor."""
+
+ sort_by: Literal["time_start"]
+ """The field to sort results by"""
+
+ sort_order: Literal["ASC", "DESC"]
+ """The order to sort results"""
+
+ to: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
+ """End time for the query in ISO 8601 format"""
diff --git a/src/cloudflare/types/zero_trust/dex/devices/isps.py b/src/cloudflare/types/zero_trust/dex/devices/isps.py
new file mode 100644
index 00000000000..86840403985
--- /dev/null
+++ b/src/cloudflare/types/zero_trust/dex/devices/isps.py
@@ -0,0 +1,86 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from ....._models import BaseModel
+
+__all__ = ["ISPs", "ISP", "ISPIP", "ISPIPLocation"]
+
+
+class ISPIPLocation(BaseModel):
+ """Geographic location information.
+
+ All fields are returned as the literal string `"REDACTED"` for callers that do not have the PII permission.
+ """
+
+ city: Optional[str] = None
+ """City name. Returned as `"REDACTED"` without PII permission."""
+
+ country_iso: Optional[str] = None
+ """Country ISO code. Returned as `"REDACTED"` without PII permission."""
+
+ state_iso: Optional[str] = None
+ """State/province ISO code. Returned as `"REDACTED"` without PII permission."""
+
+ zip: Optional[str] = None
+ """ZIP/postal code. Returned as `"REDACTED"` without PII permission."""
+
+
+class ISPIP(BaseModel):
+ """IP address information for the ISP hop.
+
+ Fields marked as PII-gated (`name`, `address`, `netmask`, and all `location` sub-fields) will be returned as the literal string `"REDACTED"` for callers that do not have the PII permission. `asn`, `aso`, and `version` are always returned regardless of PII access.
+ """
+
+ address: Optional[str] = None
+ """IP address. Returned as `"REDACTED"` without PII permission."""
+
+ asn: Optional[int] = None
+ """Autonomous System Number"""
+
+ aso: Optional[str] = None
+ """Autonomous System Organization name"""
+
+ location: Optional[ISPIPLocation] = None
+ """Geographic location information.
+
+ All fields are returned as the literal string `"REDACTED"` for callers that do
+ not have the PII permission.
+ """
+
+ name: Optional[str] = None
+ """Named IP address (reverse DNS hostname when available).
+
+ Returned as `"REDACTED"` without PII permission.
+ """
+
+ netmask: Optional[str] = None
+ """Network mask. Returned as `"REDACTED"` without PII permission."""
+
+ version: Optional[int] = None
+ """IP version (`1` for IPv4, `2` for IPv6, `0` if unknown)"""
+
+
+class ISP(BaseModel):
+ test_id: str
+ """The test that generated this result"""
+
+ test_result_id: str
+ """The specific test result"""
+
+ time_start: datetime
+ """Timestamp of when the ISP was observed"""
+
+ ip: Optional[ISPIP] = None
+ """IP address information for the ISP hop.
+
+ Fields marked as PII-gated (`name`, `address`, `netmask`, and all `location`
+ sub-fields) will be returned as the literal string `"REDACTED"` for callers that
+ do not have the PII permission. `asn`, `aso`, and `version` are always returned
+ regardless of PII access.
+ """
+
+
+class ISPs(BaseModel):
+ isps: List[ISP]
diff --git a/tests/api_resources/zero_trust/dex/devices/__init__.py b/tests/api_resources/zero_trust/dex/devices/__init__.py
new file mode 100644
index 00000000000..fd8019a9a1a
--- /dev/null
+++ b/tests/api_resources/zero_trust/dex/devices/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/zero_trust/dex/devices/test_isps.py b/tests/api_resources/zero_trust/dex/devices/test_isps.py
new file mode 100644
index 00000000000..85455da30ec
--- /dev/null
+++ b/tests/api_resources/zero_trust/dex/devices/test_isps.py
@@ -0,0 +1,162 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, Optional, cast
+
+import pytest
+
+from cloudflare import Cloudflare, AsyncCloudflare
+from tests.utils import assert_matches_type
+from cloudflare._utils import parse_datetime
+from cloudflare.pagination import SyncV4PagePagination, AsyncV4PagePagination
+from cloudflare.types.zero_trust.dex.devices import ISPs
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestISPs:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Cloudflare) -> None:
+ isp = client.zero_trust.dex.devices.isps.list(
+ device_id="f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+ account_id="01a7362d577a6c3019a474fd6f485823",
+ per_page=1,
+ )
+ assert_matches_type(SyncV4PagePagination[Optional[ISPs]], isp, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Cloudflare) -> None:
+ isp = client.zero_trust.dex.devices.isps.list(
+ device_id="f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+ account_id="01a7362d577a6c3019a474fd6f485823",
+ per_page=1,
+ cursor="cursor",
+ from_=parse_datetime("2019-12-27T18:11:19.117Z"),
+ page=1,
+ sort_by="time_start",
+ sort_order="ASC",
+ to=parse_datetime("2019-12-27T18:11:19.117Z"),
+ )
+ assert_matches_type(SyncV4PagePagination[Optional[ISPs]], isp, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Cloudflare) -> None:
+ response = client.zero_trust.dex.devices.isps.with_raw_response.list(
+ device_id="f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+ account_id="01a7362d577a6c3019a474fd6f485823",
+ per_page=1,
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ isp = response.parse()
+ assert_matches_type(SyncV4PagePagination[Optional[ISPs]], isp, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Cloudflare) -> None:
+ with client.zero_trust.dex.devices.isps.with_streaming_response.list(
+ device_id="f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+ account_id="01a7362d577a6c3019a474fd6f485823",
+ per_page=1,
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ isp = response.parse()
+ assert_matches_type(SyncV4PagePagination[Optional[ISPs]], isp, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_list(self, client: Cloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ client.zero_trust.dex.devices.isps.with_raw_response.list(
+ device_id="f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+ account_id="",
+ per_page=1,
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"):
+ client.zero_trust.dex.devices.isps.with_raw_response.list(
+ device_id="",
+ account_id="01a7362d577a6c3019a474fd6f485823",
+ per_page=1,
+ )
+
+
+class TestAsyncISPs:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncCloudflare) -> None:
+ isp = await async_client.zero_trust.dex.devices.isps.list(
+ device_id="f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+ account_id="01a7362d577a6c3019a474fd6f485823",
+ per_page=1,
+ )
+ assert_matches_type(AsyncV4PagePagination[Optional[ISPs]], isp, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncCloudflare) -> None:
+ isp = await async_client.zero_trust.dex.devices.isps.list(
+ device_id="f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+ account_id="01a7362d577a6c3019a474fd6f485823",
+ per_page=1,
+ cursor="cursor",
+ from_=parse_datetime("2019-12-27T18:11:19.117Z"),
+ page=1,
+ sort_by="time_start",
+ sort_order="ASC",
+ to=parse_datetime("2019-12-27T18:11:19.117Z"),
+ )
+ assert_matches_type(AsyncV4PagePagination[Optional[ISPs]], isp, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncCloudflare) -> None:
+ response = await async_client.zero_trust.dex.devices.isps.with_raw_response.list(
+ device_id="f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+ account_id="01a7362d577a6c3019a474fd6f485823",
+ per_page=1,
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ isp = await response.parse()
+ assert_matches_type(AsyncV4PagePagination[Optional[ISPs]], isp, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncCloudflare) -> None:
+ async with async_client.zero_trust.dex.devices.isps.with_streaming_response.list(
+ device_id="f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+ account_id="01a7362d577a6c3019a474fd6f485823",
+ per_page=1,
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ isp = await response.parse()
+ assert_matches_type(AsyncV4PagePagination[Optional[ISPs]], isp, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_list(self, async_client: AsyncCloudflare) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ await async_client.zero_trust.dex.devices.isps.with_raw_response.list(
+ device_id="f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+ account_id="",
+ per_page=1,
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"):
+ await async_client.zero_trust.dex.devices.isps.with_raw_response.list(
+ device_id="",
+ account_id="01a7362d577a6c3019a474fd6f485823",
+ per_page=1,
+ )