From 50555c636b2c733c731a04be855b962168d050b1 Mon Sep 17 00:00:00 2001
From: "kernel-internal[bot]"
<260533166+kernel-internal[bot]@users.noreply.github.com>
Date: Fri, 6 Nov 2026 18:25:28 +0000
Subject: [PATCH 1/3] Build SDK
Stainless-Generated-From: d614e63f2e20dff8ad131ca6220aa0b97fc37248
---
src/kernel/types/shared/browser_viewport.py | 10 ++++++++--
src/kernel/types/shared_params/browser_viewport.py | 10 ++++++++--
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/src/kernel/types/shared/browser_viewport.py b/src/kernel/types/shared/browser_viewport.py
index c2d85101..6d856c81 100644
--- a/src/kernel/types/shared/browser_viewport.py
+++ b/src/kernel/types/shared/browser_viewport.py
@@ -23,10 +23,16 @@ class BrowserViewport(BaseModel):
"""
height: int
- """Browser window height in pixels. Any positive integer is accepted."""
+ """Browser window height in pixels.
+
+ Any positive integer is accepted; headful browsers require at least 64.
+ """
width: int
- """Browser window width in pixels. Any positive integer is accepted."""
+ """Browser window width in pixels.
+
+ Any positive integer is accepted; headful browsers require at least 64.
+ """
refresh_rate: Optional[int] = None
"""Display refresh rate in Hz.
diff --git a/src/kernel/types/shared_params/browser_viewport.py b/src/kernel/types/shared_params/browser_viewport.py
index 3f31ee7f..47773f3a 100644
--- a/src/kernel/types/shared_params/browser_viewport.py
+++ b/src/kernel/types/shared_params/browser_viewport.py
@@ -23,10 +23,16 @@ class BrowserViewport(TypedDict, total=False):
"""
height: Required[int]
- """Browser window height in pixels. Any positive integer is accepted."""
+ """Browser window height in pixels.
+
+ Any positive integer is accepted; headful browsers require at least 64.
+ """
width: Required[int]
- """Browser window width in pixels. Any positive integer is accepted."""
+ """Browser window width in pixels.
+
+ Any positive integer is accepted; headful browsers require at least 64.
+ """
refresh_rate: int
"""Display refresh rate in Hz.
From cfda76aba98f670654ecb40853a33e8bfaeeb82e Mon Sep 17 00:00:00 2001
From: "kernel-internal[bot]"
<260533166+kernel-internal[bot]@users.noreply.github.com>
Date: Fri, 6 Nov 2026 19:18:10 +0000
Subject: [PATCH 2/3] feat: Let reserved orgs list, pin and move ISP proxy IPs
Stainless-Generated-From: e45141397c218a32003b28e1e2bb9077979936be
---
.stats.yml | 2 +-
api.md | 13 +
src/kernel/_client.py | 44 +++
src/kernel/resources/__init__.py | 14 +
src/kernel/resources/proxies.py | 60 +++-
src/kernel/resources/proxy_ips.py | 319 ++++++++++++++++++++
src/kernel/types/__init__.py | 3 +
src/kernel/types/proxy.py | 14 +-
src/kernel/types/proxy_check_response.py | 14 +-
src/kernel/types/proxy_create_params.py | 14 +-
src/kernel/types/proxy_create_response.py | 14 +-
src/kernel/types/proxy_ip.py | 24 ++
src/kernel/types/proxy_ip_detail.py | 45 +++
src/kernel/types/proxy_ip_list_params.py | 21 ++
src/kernel/types/proxy_list_params.py | 7 +
src/kernel/types/proxy_list_response.py | 14 +-
src/kernel/types/proxy_retrieve_response.py | 14 +-
src/kernel/types/proxy_update_params.py | 21 +-
src/kernel/types/proxy_update_response.py | 14 +-
tests/api_resources/test_proxies.py | 36 ++-
tests/api_resources/test_proxy_ips.py | 187 ++++++++++++
21 files changed, 866 insertions(+), 28 deletions(-)
create mode 100644 src/kernel/resources/proxy_ips.py
create mode 100644 src/kernel/types/proxy_ip.py
create mode 100644 src/kernel/types/proxy_ip_detail.py
create mode 100644 src/kernel/types/proxy_ip_list_params.py
create mode 100644 tests/api_resources/test_proxy_ips.py
diff --git a/.stats.yml b/.stats.yml
index ea0d5273..4725ed91 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1 +1 @@
-configured_endpoints: 176
+configured_endpoints: 178
diff --git a/api.md b/api.md
index 573bbb52..dada4a8b 100644
--- a/api.md
+++ b/api.md
@@ -498,6 +498,19 @@ Methods:
- client.proxies.delete(id_or_name) -> None
- client.proxies.check(id_or_name, \*\*params) -> ProxyCheckResponse
+# ProxyIPs
+
+Types:
+
+```python
+from kernel.types import ProxyIP, ProxyIPDetail
+```
+
+Methods:
+
+- client.proxy_ips.retrieve(ip_address) -> ProxyIPDetail
+- client.proxy_ips.list(\*\*params) -> SyncOffsetPagination[ProxyIP]
+
# Extensions
Types:
diff --git a/src/kernel/_client.py b/src/kernel/_client.py
index 66394d5a..1d347980 100644
--- a/src/kernel/_client.py
+++ b/src/kernel/_client.py
@@ -64,6 +64,7 @@
browsers,
profiles,
projects,
+ proxy_ips,
telemetry,
audit_logs,
extensions,
@@ -81,6 +82,7 @@
from .resources.api_keys import APIKeysResource, AsyncAPIKeysResource
from .resources.profiles import ProfilesResource, AsyncProfilesResource
from .resources.auth.auth import AuthResource, AsyncAuthResource
+ from .resources.proxy_ips import ProxyIPsResource, AsyncProxyIPsResource
from .resources.extensions import ExtensionsResource, AsyncExtensionsResource
from .resources.credentials import CredentialsResource, AsyncCredentialsResource
from .resources.deployments import DeploymentsResource, AsyncDeploymentsResource
@@ -281,6 +283,13 @@ def proxies(self) -> ProxiesResource:
return ProxiesResource(self)
+ @cached_property
+ def proxy_ips(self) -> ProxyIPsResource:
+ """Create and manage proxy configurations for routing browser traffic."""
+ from .resources.proxy_ips import ProxyIPsResource
+
+ return ProxyIPsResource(self)
+
@cached_property
def extensions(self) -> ExtensionsResource:
"""Create, list, retrieve, and delete browser extensions."""
@@ -690,6 +699,13 @@ def proxies(self) -> AsyncProxiesResource:
return AsyncProxiesResource(self)
+ @cached_property
+ def proxy_ips(self) -> AsyncProxyIPsResource:
+ """Create and manage proxy configurations for routing browser traffic."""
+ from .resources.proxy_ips import AsyncProxyIPsResource
+
+ return AsyncProxyIPsResource(self)
+
@cached_property
def extensions(self) -> AsyncExtensionsResource:
"""Create, list, retrieve, and delete browser extensions."""
@@ -999,6 +1015,13 @@ def proxies(self) -> proxies.ProxiesResourceWithRawResponse:
return ProxiesResourceWithRawResponse(self._client.proxies)
+ @cached_property
+ def proxy_ips(self) -> proxy_ips.ProxyIPsResourceWithRawResponse:
+ """Create and manage proxy configurations for routing browser traffic."""
+ from .resources.proxy_ips import ProxyIPsResourceWithRawResponse
+
+ return ProxyIPsResourceWithRawResponse(self._client.proxy_ips)
+
@cached_property
def extensions(self) -> extensions.ExtensionsResourceWithRawResponse:
"""Create, list, retrieve, and delete browser extensions."""
@@ -1140,6 +1163,13 @@ def proxies(self) -> proxies.AsyncProxiesResourceWithRawResponse:
return AsyncProxiesResourceWithRawResponse(self._client.proxies)
+ @cached_property
+ def proxy_ips(self) -> proxy_ips.AsyncProxyIPsResourceWithRawResponse:
+ """Create and manage proxy configurations for routing browser traffic."""
+ from .resources.proxy_ips import AsyncProxyIPsResourceWithRawResponse
+
+ return AsyncProxyIPsResourceWithRawResponse(self._client.proxy_ips)
+
@cached_property
def extensions(self) -> extensions.AsyncExtensionsResourceWithRawResponse:
"""Create, list, retrieve, and delete browser extensions."""
@@ -1281,6 +1311,13 @@ def proxies(self) -> proxies.ProxiesResourceWithStreamingResponse:
return ProxiesResourceWithStreamingResponse(self._client.proxies)
+ @cached_property
+ def proxy_ips(self) -> proxy_ips.ProxyIPsResourceWithStreamingResponse:
+ """Create and manage proxy configurations for routing browser traffic."""
+ from .resources.proxy_ips import ProxyIPsResourceWithStreamingResponse
+
+ return ProxyIPsResourceWithStreamingResponse(self._client.proxy_ips)
+
@cached_property
def extensions(self) -> extensions.ExtensionsResourceWithStreamingResponse:
"""Create, list, retrieve, and delete browser extensions."""
@@ -1422,6 +1459,13 @@ def proxies(self) -> proxies.AsyncProxiesResourceWithStreamingResponse:
return AsyncProxiesResourceWithStreamingResponse(self._client.proxies)
+ @cached_property
+ def proxy_ips(self) -> proxy_ips.AsyncProxyIPsResourceWithStreamingResponse:
+ """Create and manage proxy configurations for routing browser traffic."""
+ from .resources.proxy_ips import AsyncProxyIPsResourceWithStreamingResponse
+
+ return AsyncProxyIPsResourceWithStreamingResponse(self._client.proxy_ips)
+
@cached_property
def extensions(self) -> extensions.AsyncExtensionsResourceWithStreamingResponse:
"""Create, list, retrieve, and delete browser extensions."""
diff --git a/src/kernel/resources/__init__.py b/src/kernel/resources/__init__.py
index 4be8e2ba..0827e925 100644
--- a/src/kernel/resources/__init__.py
+++ b/src/kernel/resources/__init__.py
@@ -72,6 +72,14 @@
ProjectsResourceWithStreamingResponse,
AsyncProjectsResourceWithStreamingResponse,
)
+from .proxy_ips import (
+ ProxyIPsResource,
+ AsyncProxyIPsResource,
+ ProxyIPsResourceWithRawResponse,
+ AsyncProxyIPsResourceWithRawResponse,
+ ProxyIPsResourceWithStreamingResponse,
+ AsyncProxyIPsResourceWithStreamingResponse,
+)
from .telemetry import (
TelemetryResource,
AsyncTelemetryResource,
@@ -216,6 +224,12 @@
"AsyncProxiesResourceWithRawResponse",
"ProxiesResourceWithStreamingResponse",
"AsyncProxiesResourceWithStreamingResponse",
+ "ProxyIPsResource",
+ "AsyncProxyIPsResource",
+ "ProxyIPsResourceWithRawResponse",
+ "AsyncProxyIPsResourceWithRawResponse",
+ "ProxyIPsResourceWithStreamingResponse",
+ "AsyncProxyIPsResourceWithStreamingResponse",
"ExtensionsResource",
"AsyncExtensionsResource",
"ExtensionsResourceWithRawResponse",
diff --git a/src/kernel/resources/proxies.py b/src/kernel/resources/proxies.py
index d135b3b8..98f13fb2 100644
--- a/src/kernel/resources/proxies.py
+++ b/src/kernel/resources/proxies.py
@@ -150,7 +150,8 @@ def update(
self,
id_or_name: str,
*,
- name: str,
+ config: proxy_update_params.Config | Omit = omit,
+ name: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -159,12 +160,19 @@ def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
idempotency_key: str | None = None,
) -> ProxyUpdateResponse:
- """Update a proxy's name.
+ """Update a proxy's name, or move an ISP proxy to another reserved exit IP.
- Proxy names are not unique and duplicate names are
- allowed.
+ Proxy
+ names are not unique and duplicate names are allowed. Moving requires a reserved
+ ISP allocation and is accepted only for proxies of type isp: the new endpoint is
+ probed first and nothing changes if the probe fails. An IP outside the
+ allocation is a 400 with code ip_not_reserved. A 409 means the IP cannot
+ currently be assigned or the proxy was changed concurrently; read it back and
+ retry.
Args:
+ config: Changes to an ISP proxy's exit IP. Accepted only for proxies of type `isp`.
+
name: New proxy name. Proxy names are trimmed and length-checked only; duplicates are
allowed because proxies are updated by ID, not by name.
@@ -182,7 +190,13 @@ def update(
raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
return self._patch(
path_template("/proxies/{id_or_name}", id_or_name=id_or_name),
- body=maybe_transform({"name": name}, proxy_update_params.ProxyUpdateParams),
+ body=maybe_transform(
+ {
+ "config": config,
+ "name": name,
+ },
+ proxy_update_params.ProxyUpdateParams,
+ ),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -196,6 +210,7 @@ def update(
def list(
self,
*,
+ ip_address: str | Omit = omit,
limit: int | Omit = omit,
name: str | Omit = omit,
offset: int | Omit = omit,
@@ -211,6 +226,10 @@ def list(
List proxies in the resolved project.
Args:
+ ip_address: Exact match on the primary exit IP of ISP proxies. Must be an IPv4 address. Only
+ ISP proxies are matched: the ip_address shown on other proxy types is a one-time
+ observation, not a stable identity.
+
limit: Limit the number of proxies to return.
name: Exact-match filter on proxy name using the database collation. In production,
@@ -240,6 +259,7 @@ def list(
timeout=timeout,
query=maybe_transform(
{
+ "ip_address": ip_address,
"limit": limit,
"name": name,
"offset": offset,
@@ -480,7 +500,8 @@ async def update(
self,
id_or_name: str,
*,
- name: str,
+ config: proxy_update_params.Config | Omit = omit,
+ name: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -489,12 +510,19 @@ async def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
idempotency_key: str | None = None,
) -> ProxyUpdateResponse:
- """Update a proxy's name.
+ """Update a proxy's name, or move an ISP proxy to another reserved exit IP.
- Proxy names are not unique and duplicate names are
- allowed.
+ Proxy
+ names are not unique and duplicate names are allowed. Moving requires a reserved
+ ISP allocation and is accepted only for proxies of type isp: the new endpoint is
+ probed first and nothing changes if the probe fails. An IP outside the
+ allocation is a 400 with code ip_not_reserved. A 409 means the IP cannot
+ currently be assigned or the proxy was changed concurrently; read it back and
+ retry.
Args:
+ config: Changes to an ISP proxy's exit IP. Accepted only for proxies of type `isp`.
+
name: New proxy name. Proxy names are trimmed and length-checked only; duplicates are
allowed because proxies are updated by ID, not by name.
@@ -512,7 +540,13 @@ async def update(
raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
return await self._patch(
path_template("/proxies/{id_or_name}", id_or_name=id_or_name),
- body=await async_maybe_transform({"name": name}, proxy_update_params.ProxyUpdateParams),
+ body=await async_maybe_transform(
+ {
+ "config": config,
+ "name": name,
+ },
+ proxy_update_params.ProxyUpdateParams,
+ ),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -526,6 +560,7 @@ async def update(
def list(
self,
*,
+ ip_address: str | Omit = omit,
limit: int | Omit = omit,
name: str | Omit = omit,
offset: int | Omit = omit,
@@ -541,6 +576,10 @@ def list(
List proxies in the resolved project.
Args:
+ ip_address: Exact match on the primary exit IP of ISP proxies. Must be an IPv4 address. Only
+ ISP proxies are matched: the ip_address shown on other proxy types is a one-time
+ observation, not a stable identity.
+
limit: Limit the number of proxies to return.
name: Exact-match filter on proxy name using the database collation. In production,
@@ -570,6 +609,7 @@ def list(
timeout=timeout,
query=maybe_transform(
{
+ "ip_address": ip_address,
"limit": limit,
"name": name,
"offset": offset,
diff --git a/src/kernel/resources/proxy_ips.py b/src/kernel/resources/proxy_ips.py
new file mode 100644
index 00000000..cd56d37b
--- /dev/null
+++ b/src/kernel/resources/proxy_ips.py
@@ -0,0 +1,319 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal
+
+import httpx
+
+from ..types import proxy_ip_list_params
+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 SyncOffsetPagination, AsyncOffsetPagination
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.proxy_ip import ProxyIP
+from ..types.proxy_ip_detail import ProxyIPDetail
+
+__all__ = ["ProxyIPsResource", "AsyncProxyIPsResource"]
+
+
+class ProxyIPsResource(SyncAPIResource):
+ """Create and manage proxy configurations for routing browser traffic."""
+
+ @cached_property
+ def with_raw_response(self) -> ProxyIPsResourceWithRawResponse:
+ """
+ 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/kernel/kernel-python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return ProxyIPsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ProxyIPsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/kernel/kernel-python-sdk#with_streaming_response
+ """
+ return ProxyIPsResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ ip_address: 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,
+ ) -> ProxyIPDetail:
+ """
+ Retrieve one reserved IP with the ISP proxies using it as their primary exit.
+ proxy_count spans the organization; proxies lists only those in the current
+ project. Returns 404 when the IP is not in the organization's allocation and 403
+ with code reserved_isp_not_enabled when the organization has none. Scoped API
+ keys require proxies:read on the whole project or organization, not on
+ individual proxies; otherwise the operation returns 403 with code
+ insufficient_scope.
+
+ 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 ip_address:
+ raise ValueError(f"Expected a non-empty value for `ip_address` but received {ip_address!r}")
+ return self._get(
+ path_template("/proxy_ips/{ip_address}", ip_address=ip_address),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ProxyIPDetail,
+ )
+
+ def list(
+ self,
+ *,
+ country: str | Omit = omit,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ status: Literal["active", "excluded"] | 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,
+ ) -> SyncOffsetPagination[ProxyIP]:
+ """
+ List the IP addresses reserved for the organization's ISP proxies, with each
+ IP's country and whether it can currently be assigned. Available only to
+ organizations with a reserved ISP allocation; others receive a 403 with code
+ reserved_isp_not_enabled. Served from the allocation itself, so IPs appear
+ whether or not a proxy uses them. Scoped API keys require proxies:read on the
+ whole project or organization, not on individual proxies; otherwise the
+ operation returns 403 with code insufficient_scope.
+
+ Args:
+ country: ISO 3166 country code to filter by. Must be a country ISP proxies support.
+
+ limit: Limit the number of IPs to return.
+
+ offset: Offset the number of IPs to return.
+
+ status: Filter by whether the IP can currently be assigned.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/proxy_ips",
+ page=SyncOffsetPagination[ProxyIP],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "country": country,
+ "limit": limit,
+ "offset": offset,
+ "status": status,
+ },
+ proxy_ip_list_params.ProxyIPListParams,
+ ),
+ ),
+ model=ProxyIP,
+ )
+
+
+class AsyncProxyIPsResource(AsyncAPIResource):
+ """Create and manage proxy configurations for routing browser traffic."""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncProxyIPsResourceWithRawResponse:
+ """
+ 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/kernel/kernel-python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncProxyIPsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncProxyIPsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/kernel/kernel-python-sdk#with_streaming_response
+ """
+ return AsyncProxyIPsResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ ip_address: 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,
+ ) -> ProxyIPDetail:
+ """
+ Retrieve one reserved IP with the ISP proxies using it as their primary exit.
+ proxy_count spans the organization; proxies lists only those in the current
+ project. Returns 404 when the IP is not in the organization's allocation and 403
+ with code reserved_isp_not_enabled when the organization has none. Scoped API
+ keys require proxies:read on the whole project or organization, not on
+ individual proxies; otherwise the operation returns 403 with code
+ insufficient_scope.
+
+ 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 ip_address:
+ raise ValueError(f"Expected a non-empty value for `ip_address` but received {ip_address!r}")
+ return await self._get(
+ path_template("/proxy_ips/{ip_address}", ip_address=ip_address),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ProxyIPDetail,
+ )
+
+ def list(
+ self,
+ *,
+ country: str | Omit = omit,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ status: Literal["active", "excluded"] | 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[ProxyIP, AsyncOffsetPagination[ProxyIP]]:
+ """
+ List the IP addresses reserved for the organization's ISP proxies, with each
+ IP's country and whether it can currently be assigned. Available only to
+ organizations with a reserved ISP allocation; others receive a 403 with code
+ reserved_isp_not_enabled. Served from the allocation itself, so IPs appear
+ whether or not a proxy uses them. Scoped API keys require proxies:read on the
+ whole project or organization, not on individual proxies; otherwise the
+ operation returns 403 with code insufficient_scope.
+
+ Args:
+ country: ISO 3166 country code to filter by. Must be a country ISP proxies support.
+
+ limit: Limit the number of IPs to return.
+
+ offset: Offset the number of IPs to return.
+
+ status: Filter by whether the IP can currently be assigned.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/proxy_ips",
+ page=AsyncOffsetPagination[ProxyIP],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "country": country,
+ "limit": limit,
+ "offset": offset,
+ "status": status,
+ },
+ proxy_ip_list_params.ProxyIPListParams,
+ ),
+ ),
+ model=ProxyIP,
+ )
+
+
+class ProxyIPsResourceWithRawResponse:
+ def __init__(self, proxy_ips: ProxyIPsResource) -> None:
+ self._proxy_ips = proxy_ips
+
+ self.retrieve = to_raw_response_wrapper(
+ proxy_ips.retrieve,
+ )
+ self.list = to_raw_response_wrapper(
+ proxy_ips.list,
+ )
+
+
+class AsyncProxyIPsResourceWithRawResponse:
+ def __init__(self, proxy_ips: AsyncProxyIPsResource) -> None:
+ self._proxy_ips = proxy_ips
+
+ self.retrieve = async_to_raw_response_wrapper(
+ proxy_ips.retrieve,
+ )
+ self.list = async_to_raw_response_wrapper(
+ proxy_ips.list,
+ )
+
+
+class ProxyIPsResourceWithStreamingResponse:
+ def __init__(self, proxy_ips: ProxyIPsResource) -> None:
+ self._proxy_ips = proxy_ips
+
+ self.retrieve = to_streamed_response_wrapper(
+ proxy_ips.retrieve,
+ )
+ self.list = to_streamed_response_wrapper(
+ proxy_ips.list,
+ )
+
+
+class AsyncProxyIPsResourceWithStreamingResponse:
+ def __init__(self, proxy_ips: AsyncProxyIPsResource) -> None:
+ self._proxy_ips = proxy_ips
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ proxy_ips.retrieve,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ proxy_ips.list,
+ )
diff --git a/src/kernel/types/__init__.py b/src/kernel/types/__init__.py
index eb5e4193..45cc0ec4 100644
--- a/src/kernel/types/__init__.py
+++ b/src/kernel/types/__init__.py
@@ -30,6 +30,7 @@
from .warning import Warning as Warning
from .analysis import Analysis as Analysis
from .evidence import Evidence as Evidence
+from .proxy_ip import ProxyIP as ProxyIP
from .credential import Credential as Credential
from .tags_param import TagsParam as TagsParam
from .browser_pool import BrowserPool as BrowserPool
@@ -42,6 +43,7 @@
from .audit_log_entry import AuditLogEntry as AuditLogEntry
from .created_api_key import CreatedAPIKey as CreatedAPIKey
from .lookup_response import LookupResponse as LookupResponse
+from .proxy_ip_detail import ProxyIPDetail as ProxyIPDetail
from .vault_reference import VaultReference as VaultReference
from .analysis_summary import AnalysisSummary as AnalysisSummary
from .browser_location import BrowserLocation as BrowserLocation
@@ -68,6 +70,7 @@
from .browser_repl_content import BrowserReplContent as BrowserReplContent
from .browser_video_memory import BrowserVideoMemory as BrowserVideoMemory
from .proxy_check_response import ProxyCheckResponse as ProxyCheckResponse
+from .proxy_ip_list_params import ProxyIPListParams as ProxyIPListParams
from .search_create_params import SearchCreateParams as SearchCreateParams
from .vault_encryption_key import VaultEncryptionKey as VaultEncryptionKey
from .api_key_create_params import APIKeyCreateParams as APIKeyCreateParams
diff --git a/src/kernel/types/proxy.py b/src/kernel/types/proxy.py
index af5045f9..773578f2 100644
--- a/src/kernel/types/proxy.py
+++ b/src/kernel/types/proxy.py
@@ -32,7 +32,19 @@ class ConfigRegistryManagedProxyCreateConfigIspProxyConfig(BaseModel):
"""ISO 3166 country code.
Supported countries are US, GB, FR, DE, SG, and KR. Defaults to US if not
- provided.
+ provided, or to the country of ip_address when that is set.
+ """
+
+ ip_address: Optional[str] = None
+ """Reserved exit IP to pin the proxy to.
+
+ Available only to organizations with a reserved ISP allocation; the IP must be
+ active in it (see GET /proxy_ips) and, when country is also set, in that
+ country. Omit to let Kernel choose an IP. A proxy read includes it only when the
+ proxy's primary exit IP is one of the organization's reserved IPs; every ISP
+ proxy's exit IP is on the top-level ip_address. An IP outside the allocation is
+ rejected with code ip_not_reserved; one that cannot currently be assigned, with
+ 409 ip_unavailable.
"""
diff --git a/src/kernel/types/proxy_check_response.py b/src/kernel/types/proxy_check_response.py
index 75ee4655..14833e29 100644
--- a/src/kernel/types/proxy_check_response.py
+++ b/src/kernel/types/proxy_check_response.py
@@ -23,7 +23,19 @@ class ConfigIspProxyConfig(BaseModel):
"""ISO 3166 country code.
Supported countries are US, GB, FR, DE, SG, and KR. Defaults to US if not
- provided.
+ provided, or to the country of ip_address when that is set.
+ """
+
+ ip_address: Optional[str] = None
+ """Reserved exit IP to pin the proxy to.
+
+ Available only to organizations with a reserved ISP allocation; the IP must be
+ active in it (see GET /proxy_ips) and, when country is also set, in that
+ country. Omit to let Kernel choose an IP. A proxy read includes it only when the
+ proxy's primary exit IP is one of the organization's reserved IPs; every ISP
+ proxy's exit IP is on the top-level ip_address. An IP outside the allocation is
+ rejected with code ip_not_reserved; one that cannot currently be assigned, with
+ 409 ip_unavailable.
"""
diff --git a/src/kernel/types/proxy_create_params.py b/src/kernel/types/proxy_create_params.py
index 6872e9fb..22207be5 100644
--- a/src/kernel/types/proxy_create_params.py
+++ b/src/kernel/types/proxy_create_params.py
@@ -40,7 +40,19 @@ class ConfigIspProxyConfig(TypedDict, total=False):
"""ISO 3166 country code.
Supported countries are US, GB, FR, DE, SG, and KR. Defaults to US if not
- provided.
+ provided, or to the country of ip_address when that is set.
+ """
+
+ ip_address: str
+ """Reserved exit IP to pin the proxy to.
+
+ Available only to organizations with a reserved ISP allocation; the IP must be
+ active in it (see GET /proxy_ips) and, when country is also set, in that
+ country. Omit to let Kernel choose an IP. A proxy read includes it only when the
+ proxy's primary exit IP is one of the organization's reserved IPs; every ISP
+ proxy's exit IP is on the top-level ip_address. An IP outside the allocation is
+ rejected with code ip_not_reserved; one that cannot currently be assigned, with
+ 409 ip_unavailable.
"""
diff --git a/src/kernel/types/proxy_create_response.py b/src/kernel/types/proxy_create_response.py
index f4d96af8..57551507 100644
--- a/src/kernel/types/proxy_create_response.py
+++ b/src/kernel/types/proxy_create_response.py
@@ -23,7 +23,19 @@ class ConfigIspProxyConfig(BaseModel):
"""ISO 3166 country code.
Supported countries are US, GB, FR, DE, SG, and KR. Defaults to US if not
- provided.
+ provided, or to the country of ip_address when that is set.
+ """
+
+ ip_address: Optional[str] = None
+ """Reserved exit IP to pin the proxy to.
+
+ Available only to organizations with a reserved ISP allocation; the IP must be
+ active in it (see GET /proxy_ips) and, when country is also set, in that
+ country. Omit to let Kernel choose an IP. A proxy read includes it only when the
+ proxy's primary exit IP is one of the organization's reserved IPs; every ISP
+ proxy's exit IP is on the top-level ip_address. An IP outside the allocation is
+ rejected with code ip_not_reserved; one that cannot currently be assigned, with
+ 409 ip_unavailable.
"""
diff --git a/src/kernel/types/proxy_ip.py b/src/kernel/types/proxy_ip.py
new file mode 100644
index 00000000..789f058e
--- /dev/null
+++ b/src/kernel/types/proxy_ip.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["ProxyIP"]
+
+
+class ProxyIP(BaseModel):
+ """One IP address reserved for the organization's ISP proxies."""
+
+ country: str
+ """ISO 3166 country code of the IP."""
+
+ ip_address: str
+ """Reserved exit IP address."""
+
+ status: Literal["active", "excluded"]
+ """Whether the IP can currently be assigned.
+
+ An excluded IP is listed but cannot be pinned or moved to; proxies already on it
+ keep working.
+ """
diff --git a/src/kernel/types/proxy_ip_detail.py b/src/kernel/types/proxy_ip_detail.py
new file mode 100644
index 00000000..158f2699
--- /dev/null
+++ b/src/kernel/types/proxy_ip_detail.py
@@ -0,0 +1,45 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["ProxyIPDetail", "Proxy"]
+
+
+class Proxy(BaseModel):
+ """An ISP proxy whose primary exit is the reserved IP."""
+
+ id: str
+
+ name: Optional[str] = None
+
+
+class ProxyIPDetail(BaseModel):
+ """A reserved IP with the ISP proxies using it as their primary exit.
+
+ Reservations are organization-wide, so proxy_count covers every project while proxies lists only those in the current project.
+ """
+
+ country: str
+ """ISO 3166 country code of the IP."""
+
+ ip_address: str
+ """Reserved exit IP address."""
+
+ proxies: List[Proxy]
+ """ISP proxies in the current project whose primary exit IP is this address."""
+
+ proxy_count: int
+ """
+ Number of ISP proxies across the organization whose primary exit IP is this
+ address.
+ """
+
+ status: Literal["active", "excluded"]
+ """Whether the IP can currently be assigned.
+
+ An excluded IP is listed but cannot be pinned or moved to; proxies already on it
+ keep working.
+ """
diff --git a/src/kernel/types/proxy_ip_list_params.py b/src/kernel/types/proxy_ip_list_params.py
new file mode 100644
index 00000000..91ef586b
--- /dev/null
+++ b/src/kernel/types/proxy_ip_list_params.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["ProxyIPListParams"]
+
+
+class ProxyIPListParams(TypedDict, total=False):
+ country: str
+ """ISO 3166 country code to filter by. Must be a country ISP proxies support."""
+
+ limit: int
+ """Limit the number of IPs to return."""
+
+ offset: int
+ """Offset the number of IPs to return."""
+
+ status: Literal["active", "excluded"]
+ """Filter by whether the IP can currently be assigned."""
diff --git a/src/kernel/types/proxy_list_params.py b/src/kernel/types/proxy_list_params.py
index 1c7b30e6..e0ddbc07 100644
--- a/src/kernel/types/proxy_list_params.py
+++ b/src/kernel/types/proxy_list_params.py
@@ -8,6 +8,13 @@
class ProxyListParams(TypedDict, total=False):
+ ip_address: str
+ """Exact match on the primary exit IP of ISP proxies.
+
+ Must be an IPv4 address. Only ISP proxies are matched: the ip_address shown on
+ other proxy types is a one-time observation, not a stable identity.
+ """
+
limit: int
"""Limit the number of proxies to return."""
diff --git a/src/kernel/types/proxy_list_response.py b/src/kernel/types/proxy_list_response.py
index 82d1bd18..53287e19 100644
--- a/src/kernel/types/proxy_list_response.py
+++ b/src/kernel/types/proxy_list_response.py
@@ -23,7 +23,19 @@ class ConfigIspProxyConfig(BaseModel):
"""ISO 3166 country code.
Supported countries are US, GB, FR, DE, SG, and KR. Defaults to US if not
- provided.
+ provided, or to the country of ip_address when that is set.
+ """
+
+ ip_address: Optional[str] = None
+ """Reserved exit IP to pin the proxy to.
+
+ Available only to organizations with a reserved ISP allocation; the IP must be
+ active in it (see GET /proxy_ips) and, when country is also set, in that
+ country. Omit to let Kernel choose an IP. A proxy read includes it only when the
+ proxy's primary exit IP is one of the organization's reserved IPs; every ISP
+ proxy's exit IP is on the top-level ip_address. An IP outside the allocation is
+ rejected with code ip_not_reserved; one that cannot currently be assigned, with
+ 409 ip_unavailable.
"""
diff --git a/src/kernel/types/proxy_retrieve_response.py b/src/kernel/types/proxy_retrieve_response.py
index c59de78a..5d04f83a 100644
--- a/src/kernel/types/proxy_retrieve_response.py
+++ b/src/kernel/types/proxy_retrieve_response.py
@@ -23,7 +23,19 @@ class ConfigIspProxyConfig(BaseModel):
"""ISO 3166 country code.
Supported countries are US, GB, FR, DE, SG, and KR. Defaults to US if not
- provided.
+ provided, or to the country of ip_address when that is set.
+ """
+
+ ip_address: Optional[str] = None
+ """Reserved exit IP to pin the proxy to.
+
+ Available only to organizations with a reserved ISP allocation; the IP must be
+ active in it (see GET /proxy_ips) and, when country is also set, in that
+ country. Omit to let Kernel choose an IP. A proxy read includes it only when the
+ proxy's primary exit IP is one of the organization's reserved IPs; every ISP
+ proxy's exit IP is on the top-level ip_address. An IP outside the allocation is
+ rejected with code ip_not_reserved; one that cannot currently be assigned, with
+ 409 ip_unavailable.
"""
diff --git a/src/kernel/types/proxy_update_params.py b/src/kernel/types/proxy_update_params.py
index 0db118a4..2aa9ab16 100644
--- a/src/kernel/types/proxy_update_params.py
+++ b/src/kernel/types/proxy_update_params.py
@@ -4,13 +4,30 @@
from typing_extensions import Required, TypedDict
-__all__ = ["ProxyUpdateParams"]
+__all__ = ["ProxyUpdateParams", "Config"]
class ProxyUpdateParams(TypedDict, total=False):
- name: Required[str]
+ config: Config
+ """Changes to an ISP proxy's exit IP. Accepted only for proxies of type `isp`."""
+
+ name: str
"""New proxy name.
Proxy names are trimmed and length-checked only; duplicates are allowed because
proxies are updated by ID, not by name.
"""
+
+
+class Config(TypedDict, total=False):
+ """Changes to an ISP proxy's exit IP. Accepted only for proxies of type `isp`."""
+
+ ip_address: Required[str]
+ """Reserved exit IP to move the proxy to.
+
+ The IP must be active in the organization's allocation and in the proxy's
+ country. The proxy keeps its ID. The new endpoint is probed before anything
+ changes; a failed probe leaves the proxy as it was. Live browsers open new
+ connections on the new IP within a few seconds; established connections drain on
+ the old one.
+ """
diff --git a/src/kernel/types/proxy_update_response.py b/src/kernel/types/proxy_update_response.py
index 323a3731..f87186bc 100644
--- a/src/kernel/types/proxy_update_response.py
+++ b/src/kernel/types/proxy_update_response.py
@@ -23,7 +23,19 @@ class ConfigIspProxyConfig(BaseModel):
"""ISO 3166 country code.
Supported countries are US, GB, FR, DE, SG, and KR. Defaults to US if not
- provided.
+ provided, or to the country of ip_address when that is set.
+ """
+
+ ip_address: Optional[str] = None
+ """Reserved exit IP to pin the proxy to.
+
+ Available only to organizations with a reserved ISP allocation; the IP must be
+ active in it (see GET /proxy_ips) and, when country is also set, in that
+ country. Omit to let Kernel choose an IP. A proxy read includes it only when the
+ proxy's primary exit IP is one of the organization's reserved IPs; every ISP
+ proxy's exit IP is on the top-level ip_address. An IP outside the allocation is
+ rejected with code ip_not_reserved; one that cannot currently be assigned, with
+ 409 ip_unavailable.
"""
diff --git a/tests/api_resources/test_proxies.py b/tests/api_resources/test_proxies.py
index a8737804..aa01e3cf 100644
--- a/tests/api_resources/test_proxies.py
+++ b/tests/api_resources/test_proxies.py
@@ -38,7 +38,10 @@ def test_method_create_with_all_params(self, client: Kernel) -> None:
proxy = client.proxies.create(
type="isp",
bypass_hosts=["string"],
- config={"country": "US"},
+ config={
+ "country": "US",
+ "ip_address": "203.0.113.10",
+ },
name="name",
protocol="http",
)
@@ -117,6 +120,15 @@ def test_path_params_retrieve(self, client: Kernel) -> None:
def test_method_update(self, client: Kernel) -> None:
proxy = client.proxies.update(
id_or_name="id_or_name",
+ )
+ assert_matches_type(ProxyUpdateResponse, proxy, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: Kernel) -> None:
+ proxy = client.proxies.update(
+ id_or_name="id_or_name",
+ config={"ip_address": "203.0.113.10"},
name="my-renamed-proxy",
)
assert_matches_type(ProxyUpdateResponse, proxy, path=["response"])
@@ -126,7 +138,6 @@ def test_method_update(self, client: Kernel) -> None:
def test_raw_response_update(self, client: Kernel) -> None:
response = client.proxies.with_raw_response.update(
id_or_name="id_or_name",
- name="my-renamed-proxy",
)
assert response.is_closed is True
@@ -139,7 +150,6 @@ def test_raw_response_update(self, client: Kernel) -> None:
def test_streaming_response_update(self, client: Kernel) -> None:
with client.proxies.with_streaming_response.update(
id_or_name="id_or_name",
- name="my-renamed-proxy",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -155,7 +165,6 @@ def test_path_params_update(self, client: Kernel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
client.proxies.with_raw_response.update(
id_or_name="",
- name="my-renamed-proxy",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@@ -168,6 +177,7 @@ def test_method_list(self, client: Kernel) -> None:
@parametrize
def test_method_list_with_all_params(self, client: Kernel) -> None:
proxy = client.proxies.list(
+ ip_address="ip_address",
limit=1,
name="name",
offset=0,
@@ -310,7 +320,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncKernel) ->
proxy = await async_client.proxies.create(
type="isp",
bypass_hosts=["string"],
- config={"country": "US"},
+ config={
+ "country": "US",
+ "ip_address": "203.0.113.10",
+ },
name="name",
protocol="http",
)
@@ -389,6 +402,15 @@ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None:
async def test_method_update(self, async_client: AsyncKernel) -> None:
proxy = await async_client.proxies.update(
id_or_name="id_or_name",
+ )
+ assert_matches_type(ProxyUpdateResponse, proxy, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> None:
+ proxy = await async_client.proxies.update(
+ id_or_name="id_or_name",
+ config={"ip_address": "203.0.113.10"},
name="my-renamed-proxy",
)
assert_matches_type(ProxyUpdateResponse, proxy, path=["response"])
@@ -398,7 +420,6 @@ async def test_method_update(self, async_client: AsyncKernel) -> None:
async def test_raw_response_update(self, async_client: AsyncKernel) -> None:
response = await async_client.proxies.with_raw_response.update(
id_or_name="id_or_name",
- name="my-renamed-proxy",
)
assert response.is_closed is True
@@ -411,7 +432,6 @@ async def test_raw_response_update(self, async_client: AsyncKernel) -> None:
async def test_streaming_response_update(self, async_client: AsyncKernel) -> None:
async with async_client.proxies.with_streaming_response.update(
id_or_name="id_or_name",
- name="my-renamed-proxy",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -427,7 +447,6 @@ async def test_path_params_update(self, async_client: AsyncKernel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
await async_client.proxies.with_raw_response.update(
id_or_name="",
- name="my-renamed-proxy",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@@ -440,6 +459,7 @@ async def test_method_list(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> None:
proxy = await async_client.proxies.list(
+ ip_address="ip_address",
limit=1,
name="name",
offset=0,
diff --git a/tests/api_resources/test_proxy_ips.py b/tests/api_resources/test_proxy_ips.py
new file mode 100644
index 00000000..49d8e7f6
--- /dev/null
+++ b/tests/api_resources/test_proxy_ips.py
@@ -0,0 +1,187 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from kernel import Kernel, AsyncKernel
+from tests.utils import assert_matches_type
+from kernel.types import ProxyIP, ProxyIPDetail
+from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestProxyIPs:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: Kernel) -> None:
+ proxy_ip = client.proxy_ips.retrieve(
+ "ip_address",
+ )
+ assert_matches_type(ProxyIPDetail, proxy_ip, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.proxy_ips.with_raw_response.retrieve(
+ "ip_address",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ proxy_ip = response.parse()
+ assert_matches_type(ProxyIPDetail, proxy_ip, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.proxy_ips.with_streaming_response.retrieve(
+ "ip_address",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ proxy_ip = response.parse()
+ assert_matches_type(ProxyIPDetail, proxy_ip, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_retrieve(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `ip_address` but received ''"):
+ client.proxy_ips.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Kernel) -> None:
+ proxy_ip = client.proxy_ips.list()
+ assert_matches_type(SyncOffsetPagination[ProxyIP], proxy_ip, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ proxy_ip = client.proxy_ips.list(
+ country="country",
+ limit=1,
+ offset=0,
+ status="active",
+ )
+ assert_matches_type(SyncOffsetPagination[ProxyIP], proxy_ip, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.proxy_ips.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ proxy_ip = response.parse()
+ assert_matches_type(SyncOffsetPagination[ProxyIP], proxy_ip, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.proxy_ips.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ proxy_ip = response.parse()
+ assert_matches_type(SyncOffsetPagination[ProxyIP], proxy_ip, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncProxyIPs:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncKernel) -> None:
+ proxy_ip = await async_client.proxy_ips.retrieve(
+ "ip_address",
+ )
+ assert_matches_type(ProxyIPDetail, proxy_ip, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None:
+ response = await async_client.proxy_ips.with_raw_response.retrieve(
+ "ip_address",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ proxy_ip = await response.parse()
+ assert_matches_type(ProxyIPDetail, proxy_ip, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncKernel) -> None:
+ async with async_client.proxy_ips.with_streaming_response.retrieve(
+ "ip_address",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ proxy_ip = await response.parse()
+ assert_matches_type(ProxyIPDetail, proxy_ip, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `ip_address` but received ''"):
+ await async_client.proxy_ips.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncKernel) -> None:
+ proxy_ip = await async_client.proxy_ips.list()
+ assert_matches_type(AsyncOffsetPagination[ProxyIP], proxy_ip, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> None:
+ proxy_ip = await async_client.proxy_ips.list(
+ country="country",
+ limit=1,
+ offset=0,
+ status="active",
+ )
+ assert_matches_type(AsyncOffsetPagination[ProxyIP], proxy_ip, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncKernel) -> None:
+ response = await async_client.proxy_ips.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ proxy_ip = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[ProxyIP], proxy_ip, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None:
+ async with async_client.proxy_ips.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ proxy_ip = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[ProxyIP], proxy_ip, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
From 65c930fdec7023f9ee0c59f4690412202e2bbc5d Mon Sep 17 00:00:00 2001
From: "kernel-internal[bot]"
<260533166+kernel-internal[bot]@users.noreply.github.com>
Date: Fri, 6 Nov 2026 19:22:40 +0000
Subject: [PATCH 3/3] release: 0.142.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 7 +++++++
pyproject.toml | 2 +-
src/kernel/_version.py | 2 +-
4 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index ab58c193..9ad4bf82 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.141.0"
+ ".": "0.142.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 27a13734..fbb0e603 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## [0.142.0](https://github.com/kernel/kernel-python-sdk/compare/v0.141.0...v0.142.0) (2026-11-06)
+
+
+### Features
+
+* Let reserved orgs list, pin and move ISP proxy IPs ([cfda76a](https://github.com/kernel/kernel-python-sdk/commit/cfda76aba98f670654ecb40853a33e8bfaeeb82e))
+
## [0.141.0](https://github.com/kernel/kernel-python-sdk/compare/v0.140.0...v0.141.0) (2026-11-05)
diff --git a/pyproject.toml b/pyproject.toml
index ac6d9740..800a270e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "kernel"
-version = "0.141.0"
+version = "0.142.0"
description = "The official Python library for the kernel API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/kernel/_version.py b/src/kernel/_version.py
index 8483c62b..efd81788 100644
--- a/src/kernel/_version.py
+++ b/src/kernel/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "kernel"
-__version__ = "0.141.0" # x-release-please-version
+__version__ = "0.142.0" # x-release-please-version