From 2e03b55638a83a8354481f47a30910e94ff57fc3 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 10 Apr 2026 23:09:05 +0000
Subject: [PATCH 1/5] feat: Allow API keys to create API and restricted keys
(#8663)
---
.stats.yml | 8 +-
api.md | 28 +++
src/runloop_api_client/_client.py | 76 +++++++
src/runloop_api_client/resources/__init__.py | 28 +++
src/runloop_api_client/resources/apikeys.py | 197 +++++++++++++++++
.../resources/restricted_keys.py | 202 ++++++++++++++++++
src/runloop_api_client/types/__init__.py | 6 +
.../types/api_key_created_view.py | 17 ++
.../types/apikey_create_params.py | 14 ++
.../types/restricted_key_create_params.py | 18 ++
.../types/restricted_key_created_view.py | 20 ++
.../types/scope_entry_view.py | 26 +++
.../types/scope_entry_view_param.py | 23 ++
tests/api_resources/test_apikeys.py | 90 ++++++++
tests/api_resources/test_restricted_keys.py | 102 +++++++++
15 files changed, 851 insertions(+), 4 deletions(-)
create mode 100644 src/runloop_api_client/resources/apikeys.py
create mode 100644 src/runloop_api_client/resources/restricted_keys.py
create mode 100644 src/runloop_api_client/types/api_key_created_view.py
create mode 100644 src/runloop_api_client/types/apikey_create_params.py
create mode 100644 src/runloop_api_client/types/restricted_key_create_params.py
create mode 100644 src/runloop_api_client/types/restricted_key_created_view.py
create mode 100644 src/runloop_api_client/types/scope_entry_view.py
create mode 100644 src/runloop_api_client/types/scope_entry_view_param.py
create mode 100644 tests/api_resources/test_apikeys.py
create mode 100644 tests/api_resources/test_restricted_keys.py
diff --git a/.stats.yml b/.stats.yml
index 7c298b7e4..36aa46087 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 110
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-f0eb12cf4df4fa3046bd88aae4966062eb6e9703768a07a0136da2f26a2ebd56.yml
-openapi_spec_hash: cdbd63a8162f1e987e937042cbd60eea
-config_hash: 526cf0707adc54c690fc687a1c6db728
+configured_endpoints: 112
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-f1ad4729124d46b6b2f926f177680a9644c6701d7e7c87731df9bf21a6414546.yml
+openapi_spec_hash: eb49220bba68581587f2d9d4d79d0c7e
+config_hash: 636a44fb2f5d76373805b629a0f3dd35
diff --git a/api.md b/api.md
index fd95076a1..3952222dd 100644
--- a/api.md
+++ b/api.md
@@ -439,3 +439,31 @@ Methods:
- client.mcp_configs.update(id, \*\*params) -> McpConfigView
- client.mcp_configs.list(\*\*params) -> SyncMcpConfigsCursorIDPage[McpConfigView]
- client.mcp_configs.delete(id) -> McpConfigView
+
+# Apikeys
+
+Types:
+
+```python
+from runloop_api_client.types import APIKeyCreatedView, APIKeyCreateParameters
+```
+
+Methods:
+
+- client.apikeys.create(\*\*params) -> APIKeyCreatedView
+
+# RestrictedKeys
+
+Types:
+
+```python
+from runloop_api_client.types import (
+ RestrictedKeyCreatedView,
+ RestrictedKeyCreateParameters,
+ ScopeEntryView,
+)
+```
+
+Methods:
+
+- client.restricted_keys.create(\*\*params) -> RestrictedKeyCreatedView
diff --git a/src/runloop_api_client/_client.py b/src/runloop_api_client/_client.py
index 28e1e6f94..61db3a474 100644
--- a/src/runloop_api_client/_client.py
+++ b/src/runloop_api_client/_client.py
@@ -34,6 +34,7 @@
from .resources import (
axons,
agents,
+ apikeys,
objects,
secrets,
devboxes,
@@ -44,9 +45,11 @@
benchmark_jobs,
benchmark_runs,
gateway_configs,
+ restricted_keys,
network_policies,
)
from .resources.agents import AgentsResource, AsyncAgentsResource
+ from .resources.apikeys import ApikeysResource, AsyncApikeysResource
from .resources.objects import ObjectsResource, AsyncObjectsResource
from .resources.secrets import SecretsResource, AsyncSecretsResource
from .resources.benchmarks import BenchmarksResource, AsyncBenchmarksResource
@@ -56,6 +59,7 @@
from .resources.benchmark_jobs import BenchmarkJobsResource, AsyncBenchmarkJobsResource
from .resources.benchmark_runs import BenchmarkRunsResource, AsyncBenchmarkRunsResource
from .resources.gateway_configs import GatewayConfigsResource, AsyncGatewayConfigsResource
+ from .resources.restricted_keys import RestrictedKeysResource, AsyncRestrictedKeysResource
from .resources.network_policies import NetworkPoliciesResource, AsyncNetworkPoliciesResource
from .resources.devboxes.devboxes import DevboxesResource, AsyncDevboxesResource
from .resources.scenarios.scenarios import ScenariosResource, AsyncScenariosResource
@@ -198,6 +202,18 @@ def mcp_configs(self) -> McpConfigsResource:
return McpConfigsResource(self)
+ @cached_property
+ def apikeys(self) -> ApikeysResource:
+ from .resources.apikeys import ApikeysResource
+
+ return ApikeysResource(self)
+
+ @cached_property
+ def restricted_keys(self) -> RestrictedKeysResource:
+ from .resources.restricted_keys import RestrictedKeysResource
+
+ return RestrictedKeysResource(self)
+
@cached_property
def with_raw_response(self) -> RunloopWithRawResponse:
return RunloopWithRawResponse(self)
@@ -446,6 +462,18 @@ def mcp_configs(self) -> AsyncMcpConfigsResource:
return AsyncMcpConfigsResource(self)
+ @cached_property
+ def apikeys(self) -> AsyncApikeysResource:
+ from .resources.apikeys import AsyncApikeysResource
+
+ return AsyncApikeysResource(self)
+
+ @cached_property
+ def restricted_keys(self) -> AsyncRestrictedKeysResource:
+ from .resources.restricted_keys import AsyncRestrictedKeysResource
+
+ return AsyncRestrictedKeysResource(self)
+
@cached_property
def with_raw_response(self) -> AsyncRunloopWithRawResponse:
return AsyncRunloopWithRawResponse(self)
@@ -643,6 +671,18 @@ def mcp_configs(self) -> mcp_configs.McpConfigsResourceWithRawResponse:
return McpConfigsResourceWithRawResponse(self._client.mcp_configs)
+ @cached_property
+ def apikeys(self) -> apikeys.ApikeysResourceWithRawResponse:
+ from .resources.apikeys import ApikeysResourceWithRawResponse
+
+ return ApikeysResourceWithRawResponse(self._client.apikeys)
+
+ @cached_property
+ def restricted_keys(self) -> restricted_keys.RestrictedKeysResourceWithRawResponse:
+ from .resources.restricted_keys import RestrictedKeysResourceWithRawResponse
+
+ return RestrictedKeysResourceWithRawResponse(self._client.restricted_keys)
+
class AsyncRunloopWithRawResponse:
_client: AsyncRunloop
@@ -728,6 +768,18 @@ def mcp_configs(self) -> mcp_configs.AsyncMcpConfigsResourceWithRawResponse:
return AsyncMcpConfigsResourceWithRawResponse(self._client.mcp_configs)
+ @cached_property
+ def apikeys(self) -> apikeys.AsyncApikeysResourceWithRawResponse:
+ from .resources.apikeys import AsyncApikeysResourceWithRawResponse
+
+ return AsyncApikeysResourceWithRawResponse(self._client.apikeys)
+
+ @cached_property
+ def restricted_keys(self) -> restricted_keys.AsyncRestrictedKeysResourceWithRawResponse:
+ from .resources.restricted_keys import AsyncRestrictedKeysResourceWithRawResponse
+
+ return AsyncRestrictedKeysResourceWithRawResponse(self._client.restricted_keys)
+
class RunloopWithStreamedResponse:
_client: Runloop
@@ -813,6 +865,18 @@ def mcp_configs(self) -> mcp_configs.McpConfigsResourceWithStreamingResponse:
return McpConfigsResourceWithStreamingResponse(self._client.mcp_configs)
+ @cached_property
+ def apikeys(self) -> apikeys.ApikeysResourceWithStreamingResponse:
+ from .resources.apikeys import ApikeysResourceWithStreamingResponse
+
+ return ApikeysResourceWithStreamingResponse(self._client.apikeys)
+
+ @cached_property
+ def restricted_keys(self) -> restricted_keys.RestrictedKeysResourceWithStreamingResponse:
+ from .resources.restricted_keys import RestrictedKeysResourceWithStreamingResponse
+
+ return RestrictedKeysResourceWithStreamingResponse(self._client.restricted_keys)
+
class AsyncRunloopWithStreamedResponse:
_client: AsyncRunloop
@@ -898,6 +962,18 @@ def mcp_configs(self) -> mcp_configs.AsyncMcpConfigsResourceWithStreamingRespons
return AsyncMcpConfigsResourceWithStreamingResponse(self._client.mcp_configs)
+ @cached_property
+ def apikeys(self) -> apikeys.AsyncApikeysResourceWithStreamingResponse:
+ from .resources.apikeys import AsyncApikeysResourceWithStreamingResponse
+
+ return AsyncApikeysResourceWithStreamingResponse(self._client.apikeys)
+
+ @cached_property
+ def restricted_keys(self) -> restricted_keys.AsyncRestrictedKeysResourceWithStreamingResponse:
+ from .resources.restricted_keys import AsyncRestrictedKeysResourceWithStreamingResponse
+
+ return AsyncRestrictedKeysResourceWithStreamingResponse(self._client.restricted_keys)
+
Client = Runloop
diff --git a/src/runloop_api_client/resources/__init__.py b/src/runloop_api_client/resources/__init__.py
index 292f6d666..dd27ba9e4 100644
--- a/src/runloop_api_client/resources/__init__.py
+++ b/src/runloop_api_client/resources/__init__.py
@@ -16,6 +16,14 @@
AgentsResourceWithStreamingResponse,
AsyncAgentsResourceWithStreamingResponse,
)
+from .apikeys import (
+ ApikeysResource,
+ AsyncApikeysResource,
+ ApikeysResourceWithRawResponse,
+ AsyncApikeysResourceWithRawResponse,
+ ApikeysResourceWithStreamingResponse,
+ AsyncApikeysResourceWithStreamingResponse,
+)
from .objects import (
ObjectsResource,
AsyncObjectsResource,
@@ -96,6 +104,14 @@
GatewayConfigsResourceWithStreamingResponse,
AsyncGatewayConfigsResourceWithStreamingResponse,
)
+from .restricted_keys import (
+ RestrictedKeysResource,
+ AsyncRestrictedKeysResource,
+ RestrictedKeysResourceWithRawResponse,
+ AsyncRestrictedKeysResourceWithRawResponse,
+ RestrictedKeysResourceWithStreamingResponse,
+ AsyncRestrictedKeysResourceWithStreamingResponse,
+)
from .network_policies import (
NetworkPoliciesResource,
AsyncNetworkPoliciesResource,
@@ -184,4 +200,16 @@
"AsyncMcpConfigsResourceWithRawResponse",
"McpConfigsResourceWithStreamingResponse",
"AsyncMcpConfigsResourceWithStreamingResponse",
+ "ApikeysResource",
+ "AsyncApikeysResource",
+ "ApikeysResourceWithRawResponse",
+ "AsyncApikeysResourceWithRawResponse",
+ "ApikeysResourceWithStreamingResponse",
+ "AsyncApikeysResourceWithStreamingResponse",
+ "RestrictedKeysResource",
+ "AsyncRestrictedKeysResource",
+ "RestrictedKeysResourceWithRawResponse",
+ "AsyncRestrictedKeysResourceWithRawResponse",
+ "RestrictedKeysResourceWithStreamingResponse",
+ "AsyncRestrictedKeysResourceWithStreamingResponse",
]
diff --git a/src/runloop_api_client/resources/apikeys.py b/src/runloop_api_client/resources/apikeys.py
new file mode 100644
index 000000000..fcc565ac0
--- /dev/null
+++ b/src/runloop_api_client/resources/apikeys.py
@@ -0,0 +1,197 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..types import apikey_create_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import 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 .._base_client import make_request_options
+from ..types.api_key_created_view import APIKeyCreatedView
+
+__all__ = ["ApikeysResource", "AsyncApikeysResource"]
+
+
+class ApikeysResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> ApikeysResourceWithRawResponse:
+ """
+ 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/runloopai/api-client-python#accessing-raw-response-data-eg-headers
+ """
+ return ApikeysResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ApikeysResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
+ """
+ return ApikeysResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ expires_at_ms: Optional[int] | 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,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ idempotency_key: str | None = None,
+ ) -> APIKeyCreatedView:
+ """Create a new API key for the authenticated account.
+
+ Use a standard API key (ak*)
+ or a restricted key (rk*) with RESOURCE_TYPE_ACCOUNT write 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
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ return self._post(
+ "/v1/apikeys",
+ body=maybe_transform(
+ {
+ "expires_at_ms": expires_at_ms,
+ "name": name,
+ },
+ apikey_create_params.ApikeyCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=APIKeyCreatedView,
+ )
+
+
+class AsyncApikeysResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncApikeysResourceWithRawResponse:
+ """
+ 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/runloopai/api-client-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncApikeysResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncApikeysResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
+ """
+ return AsyncApikeysResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ expires_at_ms: Optional[int] | 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,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ idempotency_key: str | None = None,
+ ) -> APIKeyCreatedView:
+ """Create a new API key for the authenticated account.
+
+ Use a standard API key (ak*)
+ or a restricted key (rk*) with RESOURCE_TYPE_ACCOUNT write 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
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ return await self._post(
+ "/v1/apikeys",
+ body=await async_maybe_transform(
+ {
+ "expires_at_ms": expires_at_ms,
+ "name": name,
+ },
+ apikey_create_params.ApikeyCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=APIKeyCreatedView,
+ )
+
+
+class ApikeysResourceWithRawResponse:
+ def __init__(self, apikeys: ApikeysResource) -> None:
+ self._apikeys = apikeys
+
+ self.create = to_raw_response_wrapper(
+ apikeys.create,
+ )
+
+
+class AsyncApikeysResourceWithRawResponse:
+ def __init__(self, apikeys: AsyncApikeysResource) -> None:
+ self._apikeys = apikeys
+
+ self.create = async_to_raw_response_wrapper(
+ apikeys.create,
+ )
+
+
+class ApikeysResourceWithStreamingResponse:
+ def __init__(self, apikeys: ApikeysResource) -> None:
+ self._apikeys = apikeys
+
+ self.create = to_streamed_response_wrapper(
+ apikeys.create,
+ )
+
+
+class AsyncApikeysResourceWithStreamingResponse:
+ def __init__(self, apikeys: AsyncApikeysResource) -> None:
+ self._apikeys = apikeys
+
+ self.create = async_to_streamed_response_wrapper(
+ apikeys.create,
+ )
diff --git a/src/runloop_api_client/resources/restricted_keys.py b/src/runloop_api_client/resources/restricted_keys.py
new file mode 100644
index 000000000..6e71dfedb
--- /dev/null
+++ b/src/runloop_api_client/resources/restricted_keys.py
@@ -0,0 +1,202 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+
+import httpx
+
+from ..types import restricted_key_create_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import 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 .._base_client import make_request_options
+from ..types.scope_entry_view_param import ScopeEntryViewParam
+from ..types.restricted_key_created_view import RestrictedKeyCreatedView
+
+__all__ = ["RestrictedKeysResource", "AsyncRestrictedKeysResource"]
+
+
+class RestrictedKeysResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> RestrictedKeysResourceWithRawResponse:
+ """
+ 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/runloopai/api-client-python#accessing-raw-response-data-eg-headers
+ """
+ return RestrictedKeysResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> RestrictedKeysResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
+ """
+ return RestrictedKeysResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ expires_at_ms: Optional[int] | Omit = omit,
+ name: str | Omit = omit,
+ scopes: Iterable[ScopeEntryViewParam] | 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,
+ idempotency_key: str | None = None,
+ ) -> RestrictedKeyCreatedView:
+ """Create a restricted API key with specific resource scopes.
+
+ Use a standard API
+ key (ak*) or a restricted key (rk*) with RESOURCE_TYPE_ACCOUNT write 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
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ return self._post(
+ "/v1/restricted_keys",
+ body=maybe_transform(
+ {
+ "expires_at_ms": expires_at_ms,
+ "name": name,
+ "scopes": scopes,
+ },
+ restricted_key_create_params.RestrictedKeyCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=RestrictedKeyCreatedView,
+ )
+
+
+class AsyncRestrictedKeysResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncRestrictedKeysResourceWithRawResponse:
+ """
+ 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/runloopai/api-client-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncRestrictedKeysResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncRestrictedKeysResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response
+ """
+ return AsyncRestrictedKeysResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ expires_at_ms: Optional[int] | Omit = omit,
+ name: str | Omit = omit,
+ scopes: Iterable[ScopeEntryViewParam] | 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,
+ idempotency_key: str | None = None,
+ ) -> RestrictedKeyCreatedView:
+ """Create a restricted API key with specific resource scopes.
+
+ Use a standard API
+ key (ak*) or a restricted key (rk*) with RESOURCE_TYPE_ACCOUNT write 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
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ return await self._post(
+ "/v1/restricted_keys",
+ body=await async_maybe_transform(
+ {
+ "expires_at_ms": expires_at_ms,
+ "name": name,
+ "scopes": scopes,
+ },
+ restricted_key_create_params.RestrictedKeyCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=RestrictedKeyCreatedView,
+ )
+
+
+class RestrictedKeysResourceWithRawResponse:
+ def __init__(self, restricted_keys: RestrictedKeysResource) -> None:
+ self._restricted_keys = restricted_keys
+
+ self.create = to_raw_response_wrapper(
+ restricted_keys.create,
+ )
+
+
+class AsyncRestrictedKeysResourceWithRawResponse:
+ def __init__(self, restricted_keys: AsyncRestrictedKeysResource) -> None:
+ self._restricted_keys = restricted_keys
+
+ self.create = async_to_raw_response_wrapper(
+ restricted_keys.create,
+ )
+
+
+class RestrictedKeysResourceWithStreamingResponse:
+ def __init__(self, restricted_keys: RestrictedKeysResource) -> None:
+ self._restricted_keys = restricted_keys
+
+ self.create = to_streamed_response_wrapper(
+ restricted_keys.create,
+ )
+
+
+class AsyncRestrictedKeysResourceWithStreamingResponse:
+ def __init__(self, restricted_keys: AsyncRestrictedKeysResource) -> None:
+ self._restricted_keys = restricted_keys
+
+ self.create = async_to_streamed_response_wrapper(
+ restricted_keys.create,
+ )
diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py
index da2236119..51e6407e5 100644
--- a/src/runloop_api_client/types/__init__.py
+++ b/src/runloop_api_client/types/__init__.py
@@ -30,6 +30,7 @@
from .axon_list_params import AxonListParams as AxonListParams
from .devbox_list_view import DevboxListView as DevboxListView
from .object_list_view import ObjectListView as ObjectListView
+from .scope_entry_view import ScopeEntryView as ScopeEntryView
from .scoring_contract import ScoringContract as ScoringContract
from .scoring_function import ScoringFunction as ScoringFunction
from .secret_list_view import SecretListView as SecretListView
@@ -49,6 +50,8 @@
from .input_context_param import InputContextParam as InputContextParam
from .network_policy_view import NetworkPolicyView as NetworkPolicyView
from .publish_result_view import PublishResultView as PublishResultView
+from .api_key_created_view import APIKeyCreatedView as APIKeyCreatedView
+from .apikey_create_params import ApikeyCreateParams as ApikeyCreateParams
from .devbox_create_params import DevboxCreateParams as DevboxCreateParams
from .devbox_snapshot_view import DevboxSnapshotView as DevboxSnapshotView
from .devbox_update_params import DevboxUpdateParams as DevboxUpdateParams
@@ -68,6 +71,7 @@
from .scenario_create_params import ScenarioCreateParams as ScenarioCreateParams
from .scenario_run_list_view import ScenarioRunListView as ScenarioRunListView
from .scenario_update_params import ScenarioUpdateParams as ScenarioUpdateParams
+from .scope_entry_view_param import ScopeEntryViewParam as ScopeEntryViewParam
from .scoring_contract_param import ScoringContractParam as ScoringContractParam
from .scoring_function_param import ScoringFunctionParam as ScoringFunctionParam
from .benchmark_create_params import BenchmarkCreateParams as BenchmarkCreateParams
@@ -103,6 +107,7 @@
from .devbox_enable_tunnel_params import DevboxEnableTunnelParams as DevboxEnableTunnelParams
from .devbox_execute_async_params import DevboxExecuteAsyncParams as DevboxExecuteAsyncParams
from .devbox_snapshot_disk_params import DevboxSnapshotDiskParams as DevboxSnapshotDiskParams
+from .restricted_key_created_view import RestrictedKeyCreatedView as RestrictedKeyCreatedView
from .scenario_list_public_params import ScenarioListPublicParams as ScenarioListPublicParams
from .benchmark_definitions_params import BenchmarkDefinitionsParams as BenchmarkDefinitionsParams
from .benchmark_list_public_params import BenchmarkListPublicParams as BenchmarkListPublicParams
@@ -112,6 +117,7 @@
from .gateway_config_update_params import GatewayConfigUpdateParams as GatewayConfigUpdateParams
from .network_policy_create_params import NetworkPolicyCreateParams as NetworkPolicyCreateParams
from .network_policy_update_params import NetworkPolicyUpdateParams as NetworkPolicyUpdateParams
+from .restricted_key_create_params import RestrictedKeyCreateParams as RestrictedKeyCreateParams
from .scoring_contract_result_view import ScoringContractResultView as ScoringContractResultView
from .scoring_function_result_view import ScoringFunctionResultView as ScoringFunctionResultView
from .scenario_definition_list_view import ScenarioDefinitionListView as ScenarioDefinitionListView
diff --git a/src/runloop_api_client/types/api_key_created_view.py b/src/runloop_api_client/types/api_key_created_view.py
new file mode 100644
index 000000000..f48902c64
--- /dev/null
+++ b/src/runloop_api_client/types/api_key_created_view.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+
+__all__ = ["APIKeyCreatedView"]
+
+
+class APIKeyCreatedView(BaseModel):
+ id: Optional[str] = None
+
+ expires_at_ms: Optional[int] = None
+
+ key_secret: Optional[str] = None
+
+ name: Optional[str] = None
diff --git a/src/runloop_api_client/types/apikey_create_params.py b/src/runloop_api_client/types/apikey_create_params.py
new file mode 100644
index 000000000..106427199
--- /dev/null
+++ b/src/runloop_api_client/types/apikey_create_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+__all__ = ["ApikeyCreateParams"]
+
+
+class ApikeyCreateParams(TypedDict, total=False):
+ expires_at_ms: Optional[int]
+
+ name: str
diff --git a/src/runloop_api_client/types/restricted_key_create_params.py b/src/runloop_api_client/types/restricted_key_create_params.py
new file mode 100644
index 000000000..420e65a4e
--- /dev/null
+++ b/src/runloop_api_client/types/restricted_key_create_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 import Iterable, Optional
+from typing_extensions import TypedDict
+
+from .scope_entry_view_param import ScopeEntryViewParam
+
+__all__ = ["RestrictedKeyCreateParams"]
+
+
+class RestrictedKeyCreateParams(TypedDict, total=False):
+ expires_at_ms: Optional[int]
+
+ name: str
+
+ scopes: Iterable[ScopeEntryViewParam]
diff --git a/src/runloop_api_client/types/restricted_key_created_view.py b/src/runloop_api_client/types/restricted_key_created_view.py
new file mode 100644
index 000000000..dab3875db
--- /dev/null
+++ b/src/runloop_api_client/types/restricted_key_created_view.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from .._models import BaseModel
+from .scope_entry_view import ScopeEntryView
+
+__all__ = ["RestrictedKeyCreatedView"]
+
+
+class RestrictedKeyCreatedView(BaseModel):
+ id: Optional[str] = None
+
+ expires_at_ms: Optional[int] = None
+
+ key_secret: Optional[str] = None
+
+ name: Optional[str] = None
+
+ scopes: Optional[List[ScopeEntryView]] = None
diff --git a/src/runloop_api_client/types/scope_entry_view.py b/src/runloop_api_client/types/scope_entry_view.py
new file mode 100644
index 000000000..1a0903895
--- /dev/null
+++ b/src/runloop_api_client/types/scope_entry_view.py
@@ -0,0 +1,26 @@
+# 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__ = ["ScopeEntryView"]
+
+
+class ScopeEntryView(BaseModel):
+ access_level: Optional[Literal["ACCESS_LEVEL_NONE", "ACCESS_LEVEL_READ", "ACCESS_LEVEL_WRITE"]] = None
+
+ resource_type: Optional[
+ Literal[
+ "RESOURCE_TYPE_DEVBOXES",
+ "RESOURCE_TYPE_BLUEPRINTS",
+ "RESOURCE_TYPE_SNAPSHOTS",
+ "RESOURCE_TYPE_BENCHMARKS",
+ "RESOURCE_TYPE_SCENARIOS",
+ "RESOURCE_TYPE_REPO_CONNECTIONS",
+ "RESOURCE_TYPE_AGENTS",
+ "RESOURCE_TYPE_OBJECTS",
+ "RESOURCE_TYPE_ACCOUNT",
+ ]
+ ] = None
diff --git a/src/runloop_api_client/types/scope_entry_view_param.py b/src/runloop_api_client/types/scope_entry_view_param.py
new file mode 100644
index 000000000..5379b838a
--- /dev/null
+++ b/src/runloop_api_client/types/scope_entry_view_param.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["ScopeEntryViewParam"]
+
+
+class ScopeEntryViewParam(TypedDict, total=False):
+ access_level: Literal["ACCESS_LEVEL_NONE", "ACCESS_LEVEL_READ", "ACCESS_LEVEL_WRITE"]
+
+ resource_type: Literal[
+ "RESOURCE_TYPE_DEVBOXES",
+ "RESOURCE_TYPE_BLUEPRINTS",
+ "RESOURCE_TYPE_SNAPSHOTS",
+ "RESOURCE_TYPE_BENCHMARKS",
+ "RESOURCE_TYPE_SCENARIOS",
+ "RESOURCE_TYPE_REPO_CONNECTIONS",
+ "RESOURCE_TYPE_AGENTS",
+ "RESOURCE_TYPE_OBJECTS",
+ "RESOURCE_TYPE_ACCOUNT",
+ ]
diff --git a/tests/api_resources/test_apikeys.py b/tests/api_resources/test_apikeys.py
new file mode 100644
index 000000000..e96ff6111
--- /dev/null
+++ b/tests/api_resources/test_apikeys.py
@@ -0,0 +1,90 @@
+# 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 tests.utils import assert_matches_type
+from runloop_api_client import Runloop, AsyncRunloop
+from runloop_api_client.types import APIKeyCreatedView
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestApikeys:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: Runloop) -> None:
+ apikey = client.apikeys.create()
+ assert_matches_type(APIKeyCreatedView, apikey, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: Runloop) -> None:
+ apikey = client.apikeys.create(
+ expires_at_ms=0,
+ name="name",
+ )
+ assert_matches_type(APIKeyCreatedView, apikey, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: Runloop) -> None:
+ response = client.apikeys.with_raw_response.create()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ apikey = response.parse()
+ assert_matches_type(APIKeyCreatedView, apikey, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: Runloop) -> None:
+ with client.apikeys.with_streaming_response.create() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ apikey = response.parse()
+ assert_matches_type(APIKeyCreatedView, apikey, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncApikeys:
+ 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: AsyncRunloop) -> None:
+ apikey = await async_client.apikeys.create()
+ assert_matches_type(APIKeyCreatedView, apikey, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None:
+ apikey = await async_client.apikeys.create(
+ expires_at_ms=0,
+ name="name",
+ )
+ assert_matches_type(APIKeyCreatedView, apikey, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncRunloop) -> None:
+ response = await async_client.apikeys.with_raw_response.create()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ apikey = await response.parse()
+ assert_matches_type(APIKeyCreatedView, apikey, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None:
+ async with async_client.apikeys.with_streaming_response.create() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ apikey = await response.parse()
+ assert_matches_type(APIKeyCreatedView, apikey, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_restricted_keys.py b/tests/api_resources/test_restricted_keys.py
new file mode 100644
index 000000000..5e2bc4fed
--- /dev/null
+++ b/tests/api_resources/test_restricted_keys.py
@@ -0,0 +1,102 @@
+# 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 tests.utils import assert_matches_type
+from runloop_api_client import Runloop, AsyncRunloop
+from runloop_api_client.types import RestrictedKeyCreatedView
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestRestrictedKeys:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: Runloop) -> None:
+ restricted_key = client.restricted_keys.create()
+ assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: Runloop) -> None:
+ restricted_key = client.restricted_keys.create(
+ expires_at_ms=0,
+ name="name",
+ scopes=[
+ {
+ "access_level": "ACCESS_LEVEL_NONE",
+ "resource_type": "RESOURCE_TYPE_DEVBOXES",
+ }
+ ],
+ )
+ assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: Runloop) -> None:
+ response = client.restricted_keys.with_raw_response.create()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ restricted_key = response.parse()
+ assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: Runloop) -> None:
+ with client.restricted_keys.with_streaming_response.create() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ restricted_key = response.parse()
+ assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncRestrictedKeys:
+ 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: AsyncRunloop) -> None:
+ restricted_key = await async_client.restricted_keys.create()
+ assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None:
+ restricted_key = await async_client.restricted_keys.create(
+ expires_at_ms=0,
+ name="name",
+ scopes=[
+ {
+ "access_level": "ACCESS_LEVEL_NONE",
+ "resource_type": "RESOURCE_TYPE_DEVBOXES",
+ }
+ ],
+ )
+ assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncRunloop) -> None:
+ response = await async_client.restricted_keys.with_raw_response.create()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ restricted_key = await response.parse()
+ assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None:
+ async with async_client.restricted_keys.with_streaming_response.create() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ restricted_key = await response.parse()
+ assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
From f7ca2cc280f1e7a4ab73f331be8fe3e33ae1c611 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 11 Apr 2026 02:59:37 +0000
Subject: [PATCH 2/5] fix: ensure file data are only sent as 1 parameter
---
src/runloop_api_client/_utils/_utils.py | 5 +++--
tests/test_extract_files.py | 9 +++++++++
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/src/runloop_api_client/_utils/_utils.py b/src/runloop_api_client/_utils/_utils.py
index eec7f4a1f..63b8cd602 100644
--- a/src/runloop_api_client/_utils/_utils.py
+++ b/src/runloop_api_client/_utils/_utils.py
@@ -86,8 +86,9 @@ def _extract_items(
index += 1
if is_dict(obj):
try:
- # We are at the last entry in the path so we must remove the field
- if (len(path)) == index:
+ # Remove the field if there are no more dict keys in the path,
+ # only "" traversal markers or end.
+ if all(p == "" for p in path[index:]):
item = obj.pop(key)
else:
item = obj[key]
diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py
index 41498b836..a76b07d19 100644
--- a/tests/test_extract_files.py
+++ b/tests/test_extract_files.py
@@ -35,6 +35,15 @@ def test_multiple_files() -> None:
assert query == {"documents": [{}, {}]}
+def test_top_level_file_array() -> None:
+ query = {"files": [b"file one", b"file two"], "title": "hello"}
+ assert extract_files(query, paths=[["files", ""]]) == [
+ ("files[]", b"file one"),
+ ("files[]", b"file two"),
+ ]
+ assert query == {"title": "hello"}
+
+
@pytest.mark.parametrize(
"query,paths,expected",
[
From 255099a940a7892f3c66e6534517e3545d7daccb Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 13 Apr 2026 03:06:14 +0000
Subject: [PATCH 3/5] feat: Add wake on axon event as an API primitive (#8681)
---
.stats.yml | 4 +--
.../types/shared/launch_parameters.py | 3 ++
.../types/shared_params/launch_parameters.py | 3 ++
tests/api_resources/test_benchmarks.py | 10 +++++--
tests/api_resources/test_blueprints.py | 30 +++++++++++++++----
tests/api_resources/test_devboxes.py | 10 +++++--
tests/api_resources/test_scenarios.py | 30 +++++++++++++++----
7 files changed, 72 insertions(+), 18 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 36aa46087..bddb40ca1 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 112
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-f1ad4729124d46b6b2f926f177680a9644c6701d7e7c87731df9bf21a6414546.yml
-openapi_spec_hash: eb49220bba68581587f2d9d4d79d0c7e
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-5b536a11a713dd4e47b270c130368dbfdf1f30282f262c160cd0411fcd291806.yml
+openapi_spec_hash: f94d993a7f34461ebde7d0186e8e3c3a
config_hash: 636a44fb2f5d76373805b629a0f3dd35
diff --git a/src/runloop_api_client/types/shared/launch_parameters.py b/src/runloop_api_client/types/shared/launch_parameters.py
index a1b52ec03..6e74af438 100644
--- a/src/runloop_api_client/types/shared/launch_parameters.py
+++ b/src/runloop_api_client/types/shared/launch_parameters.py
@@ -12,6 +12,9 @@
class LifecycleResumeTriggers(BaseModel):
"""Triggers that can resume a suspended Devbox."""
+ axon_event: Optional[bool] = None
+ """When true, axon events targeting a suspended Devbox will trigger a resume."""
+
http: Optional[bool] = None
"""When true, HTTP traffic to a suspended Devbox via tunnel will trigger a resume."""
diff --git a/src/runloop_api_client/types/shared_params/launch_parameters.py b/src/runloop_api_client/types/shared_params/launch_parameters.py
index 3906163bf..44ced4761 100644
--- a/src/runloop_api_client/types/shared_params/launch_parameters.py
+++ b/src/runloop_api_client/types/shared_params/launch_parameters.py
@@ -14,6 +14,9 @@
class LifecycleResumeTriggers(TypedDict, total=False):
"""Triggers that can resume a suspended Devbox."""
+ axon_event: Optional[bool]
+ """When true, axon events targeting a suspended Devbox will trigger a resume."""
+
http: Optional[bool]
"""When true, HTTP traffic to a suspended Devbox via tunnel will trigger a resume."""
diff --git a/tests/api_resources/test_benchmarks.py b/tests/api_resources/test_benchmarks.py
index 924deb133..7612b34f2 100644
--- a/tests/api_resources/test_benchmarks.py
+++ b/tests/api_resources/test_benchmarks.py
@@ -304,7 +304,10 @@ def test_method_start_run_with_all_params(self, client: Runloop) -> None:
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
@@ -686,7 +689,10 @@ async def test_method_start_run_with_all_params(self, async_client: AsyncRunloop
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
diff --git a/tests/api_resources/test_blueprints.py b/tests/api_resources/test_blueprints.py
index 89afc9e4b..dd3e19fcb 100644
--- a/tests/api_resources/test_blueprints.py
+++ b/tests/api_resources/test_blueprints.py
@@ -70,7 +70,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None:
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
@@ -292,7 +295,10 @@ def test_method_create_from_inspection_with_all_params(self, client: Runloop) ->
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
@@ -458,7 +464,10 @@ def test_method_preview_with_all_params(self, client: Runloop) -> None:
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
@@ -567,7 +576,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
@@ -789,7 +801,10 @@ async def test_method_create_from_inspection_with_all_params(self, async_client:
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
@@ -955,7 +970,10 @@ async def test_method_preview_with_all_params(self, async_client: AsyncRunloop)
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py
index ec948f719..920ff437a 100644
--- a/tests/api_resources/test_devboxes.py
+++ b/tests/api_resources/test_devboxes.py
@@ -90,7 +90,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None:
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
@@ -1714,7 +1717,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
diff --git a/tests/api_resources/test_scenarios.py b/tests/api_resources/test_scenarios.py
index 748d153c8..9dd8c3e63 100644
--- a/tests/api_resources/test_scenarios.py
+++ b/tests/api_resources/test_scenarios.py
@@ -83,7 +83,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None:
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
@@ -225,7 +228,10 @@ def test_method_update_with_all_params(self, client: Runloop) -> None:
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
@@ -440,7 +446,10 @@ def test_method_start_run_with_all_params(self, client: Runloop) -> None:
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
@@ -555,7 +564,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
@@ -697,7 +709,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncRunloop) -
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
@@ -912,7 +927,10 @@ async def test_method_start_run_with_all_params(self, async_client: AsyncRunloop
"idle_time_seconds": 0,
"on_idle": "shutdown",
},
- "resume_triggers": {"http": True},
+ "resume_triggers": {
+ "axon_event": True,
+ "http": True,
+ },
},
"network_policy_id": "network_policy_id",
"required_services": ["string"],
From 4f6fe60a63fd31c049e54febb81801031dce2325 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 13 Apr 2026 23:29:10 +0000
Subject: [PATCH 4/5] fix: add missing agent API paths to stainless config
(#8699)
---
.stats.yml | 4 +-
api.md | 10 +-
src/runloop_api_client/resources/agents.py | 297 +++++++++++++++++-
src/runloop_api_client/types/__init__.py | 2 +
.../types/agent_devbox_counts_view.py | 24 ++
.../types/agent_list_public_params.py | 30 ++
tests/api_resources/test_agents.py | 205 +++++++++++-
7 files changed, 567 insertions(+), 5 deletions(-)
create mode 100644 src/runloop_api_client/types/agent_devbox_counts_view.py
create mode 100644 src/runloop_api_client/types/agent_list_public_params.py
diff --git a/.stats.yml b/.stats.yml
index bddb40ca1..e948713b6 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 112
+configured_endpoints: 115
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-5b536a11a713dd4e47b270c130368dbfdf1f30282f262c160cd0411fcd291806.yml
openapi_spec_hash: f94d993a7f34461ebde7d0186e8e3c3a
-config_hash: 636a44fb2f5d76373805b629a0f3dd35
+config_hash: 12de9459ff629b6a3072a75b236b7b70
diff --git a/api.md b/api.md
index 3952222dd..1d97dc90c 100644
--- a/api.md
+++ b/api.md
@@ -79,7 +79,12 @@ Methods:
Types:
```python
-from runloop_api_client.types import AgentCreateParameters, AgentListView, AgentView
+from runloop_api_client.types import (
+ AgentCreateParameters,
+ AgentDevboxCountsView,
+ AgentListView,
+ AgentView,
+)
```
Methods:
@@ -87,6 +92,9 @@ Methods:
- client.agents.create(\*\*params) -> AgentView
- client.agents.retrieve(id) -> AgentView
- client.agents.list(\*\*params) -> SyncAgentsCursorIDPage[AgentView]
+- client.agents.delete(id) -> object
+- client.agents.devbox_counts() -> AgentDevboxCountsView
+- client.agents.list_public(\*\*params) -> SyncAgentsCursorIDPage[AgentView]
# Axons
diff --git a/src/runloop_api_client/resources/agents.py b/src/runloop_api_client/resources/agents.py
index 082d5725c..6febe22f0 100644
--- a/src/runloop_api_client/resources/agents.py
+++ b/src/runloop_api_client/resources/agents.py
@@ -6,7 +6,7 @@
import httpx
-from ..types import agent_list_params, agent_create_params
+from ..types import agent_list_params, agent_create_params, agent_list_public_params
from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from .._utils import path_template, maybe_transform, async_maybe_transform
from .._compat import cached_property
@@ -20,6 +20,7 @@
from ..pagination import SyncAgentsCursorIDPage, AsyncAgentsCursorIDPage
from .._base_client import AsyncPaginator, make_request_options
from ..types.agent_view import AgentView
+from ..types.agent_devbox_counts_view import AgentDevboxCountsView
from ..types.shared_params.agent_source import AgentSource
__all__ = ["AgentsResource", "AsyncAgentsResource"]
@@ -202,6 +203,135 @@ def list(
model=AgentView,
)
+ def delete(
+ self,
+ 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,
+ idempotency_key: str | None = None,
+ ) -> object:
+ """Delete an Agent by its unique identifier.
+
+ The Agent will be permanently removed.
+
+ 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
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._post(
+ path_template("/v1/agents/{id}/delete", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=object,
+ )
+
+ def devbox_counts(
+ self,
+ *,
+ # 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,
+ ) -> AgentDevboxCountsView:
+ """Returns devbox counts grouped by agent name.
+
+ This endpoint efficiently
+ aggregates devbox counts for all agents in a single request, avoiding N+1 query
+ patterns.
+ """
+ return self._get(
+ "/v1/agents/devbox_counts",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AgentDevboxCountsView,
+ )
+
+ def list_public(
+ self,
+ *,
+ include_total_count: bool | Omit = omit,
+ limit: int | Omit = omit,
+ name: str | Omit = omit,
+ search: str | Omit = omit,
+ starting_after: str | Omit = omit,
+ version: 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,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SyncAgentsCursorIDPage[AgentView]:
+ """
+ List all public Agents with pagination support.
+
+ Args:
+ include_total_count: If true (default), includes total_count in the response. Set to false to skip
+ the count query for better performance on large datasets.
+
+ limit: The limit of items to return. Default is 20. Max is 5000.
+
+ name: Filter agents by name (partial match supported).
+
+ search: Search by agent ID or name.
+
+ starting_after: Load the next page of data starting after the item with the given ID.
+
+ version: Filter by version. Use 'latest' to get the most recently created agent.
+
+ 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(
+ "/v1/agents/list_public",
+ page=SyncAgentsCursorIDPage[AgentView],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "include_total_count": include_total_count,
+ "limit": limit,
+ "name": name,
+ "search": search,
+ "starting_after": starting_after,
+ "version": version,
+ },
+ agent_list_public_params.AgentListPublicParams,
+ ),
+ ),
+ model=AgentView,
+ )
+
class AsyncAgentsResource(AsyncAPIResource):
@cached_property
@@ -380,6 +510,135 @@ def list(
model=AgentView,
)
+ async def delete(
+ self,
+ 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,
+ idempotency_key: str | None = None,
+ ) -> object:
+ """Delete an Agent by its unique identifier.
+
+ The Agent will be permanently removed.
+
+ 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
+
+ idempotency_key: Specify a custom idempotency key for this request
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._post(
+ path_template("/v1/agents/{id}/delete", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=object,
+ )
+
+ async def devbox_counts(
+ self,
+ *,
+ # 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,
+ ) -> AgentDevboxCountsView:
+ """Returns devbox counts grouped by agent name.
+
+ This endpoint efficiently
+ aggregates devbox counts for all agents in a single request, avoiding N+1 query
+ patterns.
+ """
+ return await self._get(
+ "/v1/agents/devbox_counts",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AgentDevboxCountsView,
+ )
+
+ def list_public(
+ self,
+ *,
+ include_total_count: bool | Omit = omit,
+ limit: int | Omit = omit,
+ name: str | Omit = omit,
+ search: str | Omit = omit,
+ starting_after: str | Omit = omit,
+ version: 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,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncPaginator[AgentView, AsyncAgentsCursorIDPage[AgentView]]:
+ """
+ List all public Agents with pagination support.
+
+ Args:
+ include_total_count: If true (default), includes total_count in the response. Set to false to skip
+ the count query for better performance on large datasets.
+
+ limit: The limit of items to return. Default is 20. Max is 5000.
+
+ name: Filter agents by name (partial match supported).
+
+ search: Search by agent ID or name.
+
+ starting_after: Load the next page of data starting after the item with the given ID.
+
+ version: Filter by version. Use 'latest' to get the most recently created agent.
+
+ 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(
+ "/v1/agents/list_public",
+ page=AsyncAgentsCursorIDPage[AgentView],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "include_total_count": include_total_count,
+ "limit": limit,
+ "name": name,
+ "search": search,
+ "starting_after": starting_after,
+ "version": version,
+ },
+ agent_list_public_params.AgentListPublicParams,
+ ),
+ ),
+ model=AgentView,
+ )
+
class AgentsResourceWithRawResponse:
def __init__(self, agents: AgentsResource) -> None:
@@ -394,6 +653,15 @@ def __init__(self, agents: AgentsResource) -> None:
self.list = to_raw_response_wrapper(
agents.list,
)
+ self.delete = to_raw_response_wrapper(
+ agents.delete,
+ )
+ self.devbox_counts = to_raw_response_wrapper(
+ agents.devbox_counts,
+ )
+ self.list_public = to_raw_response_wrapper(
+ agents.list_public,
+ )
class AsyncAgentsResourceWithRawResponse:
@@ -409,6 +677,15 @@ def __init__(self, agents: AsyncAgentsResource) -> None:
self.list = async_to_raw_response_wrapper(
agents.list,
)
+ self.delete = async_to_raw_response_wrapper(
+ agents.delete,
+ )
+ self.devbox_counts = async_to_raw_response_wrapper(
+ agents.devbox_counts,
+ )
+ self.list_public = async_to_raw_response_wrapper(
+ agents.list_public,
+ )
class AgentsResourceWithStreamingResponse:
@@ -424,6 +701,15 @@ def __init__(self, agents: AgentsResource) -> None:
self.list = to_streamed_response_wrapper(
agents.list,
)
+ self.delete = to_streamed_response_wrapper(
+ agents.delete,
+ )
+ self.devbox_counts = to_streamed_response_wrapper(
+ agents.devbox_counts,
+ )
+ self.list_public = to_streamed_response_wrapper(
+ agents.list_public,
+ )
class AsyncAgentsResourceWithStreamingResponse:
@@ -439,3 +725,12 @@ def __init__(self, agents: AsyncAgentsResource) -> None:
self.list = async_to_streamed_response_wrapper(
agents.list,
)
+ self.delete = async_to_streamed_response_wrapper(
+ agents.delete,
+ )
+ self.devbox_counts = async_to_streamed_response_wrapper(
+ agents.devbox_counts,
+ )
+ self.list_public = async_to_streamed_response_wrapper(
+ agents.list_public,
+ )
diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py
index 51e6407e5..01130880d 100644
--- a/src/runloop_api_client/types/__init__.py
+++ b/src/runloop_api_client/types/__init__.py
@@ -80,6 +80,8 @@
from .benchmark_update_params import BenchmarkUpdateParams as BenchmarkUpdateParams
from .blueprint_create_params import BlueprintCreateParams as BlueprintCreateParams
from .inspection_source_param import InspectionSourceParam as InspectionSourceParam
+from .agent_devbox_counts_view import AgentDevboxCountsView as AgentDevboxCountsView
+from .agent_list_public_params import AgentListPublicParams as AgentListPublicParams
from .blueprint_preview_params import BlueprintPreviewParams as BlueprintPreviewParams
from .gateway_config_list_view import GatewayConfigListView as GatewayConfigListView
from .mcp_config_create_params import McpConfigCreateParams as McpConfigCreateParams
diff --git a/src/runloop_api_client/types/agent_devbox_counts_view.py b/src/runloop_api_client/types/agent_devbox_counts_view.py
new file mode 100644
index 000000000..af9006d1e
--- /dev/null
+++ b/src/runloop_api_client/types/agent_devbox_counts_view.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict
+
+from .._models import BaseModel
+
+__all__ = ["AgentDevboxCountsView"]
+
+
+class AgentDevboxCountsView(BaseModel):
+ """Devbox counts grouped by agent name.
+
+ Used to efficiently fetch devbox counts for multiple agents in a single request.
+ """
+
+ counts: Dict[str, int]
+ """Map of agent name to devbox count.
+
+ Each key is an agent name, and the value is the count of devboxes associated
+ with that agent.
+ """
+
+ total_count: int
+ """Total count of devboxes across all agents in the result."""
diff --git a/src/runloop_api_client/types/agent_list_public_params.py b/src/runloop_api_client/types/agent_list_public_params.py
new file mode 100644
index 000000000..a0e53d503
--- /dev/null
+++ b/src/runloop_api_client/types/agent_list_public_params.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["AgentListPublicParams"]
+
+
+class AgentListPublicParams(TypedDict, total=False):
+ include_total_count: bool
+ """If true (default), includes total_count in the response.
+
+ Set to false to skip the count query for better performance on large datasets.
+ """
+
+ limit: int
+ """The limit of items to return. Default is 20. Max is 5000."""
+
+ name: str
+ """Filter agents by name (partial match supported)."""
+
+ search: str
+ """Search by agent ID or name."""
+
+ starting_after: str
+ """Load the next page of data starting after the item with the given ID."""
+
+ version: str
+ """Filter by version. Use 'latest' to get the most recently created agent."""
diff --git a/tests/api_resources/test_agents.py b/tests/api_resources/test_agents.py
index a6304d70d..fb602d148 100644
--- a/tests/api_resources/test_agents.py
+++ b/tests/api_resources/test_agents.py
@@ -9,7 +9,10 @@
from tests.utils import assert_matches_type
from runloop_api_client import Runloop, AsyncRunloop
-from runloop_api_client.types import AgentView
+from runloop_api_client.types import (
+ AgentView,
+ AgentDevboxCountsView,
+)
from runloop_api_client.pagination import SyncAgentsCursorIDPage, AsyncAgentsCursorIDPage
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -158,6 +161,106 @@ def test_streaming_response_list(self, client: Runloop) -> None:
assert cast(Any, response.is_closed) is True
+ @parametrize
+ def test_method_delete(self, client: Runloop) -> None:
+ agent = client.agents.delete(
+ "id",
+ )
+ assert_matches_type(object, agent, path=["response"])
+
+ @parametrize
+ def test_raw_response_delete(self, client: Runloop) -> None:
+ response = client.agents.with_raw_response.delete(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = response.parse()
+ assert_matches_type(object, agent, path=["response"])
+
+ @parametrize
+ def test_streaming_response_delete(self, client: Runloop) -> None:
+ with client.agents.with_streaming_response.delete(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = response.parse()
+ assert_matches_type(object, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_delete(self, client: Runloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.agents.with_raw_response.delete(
+ "",
+ )
+
+ @parametrize
+ def test_method_devbox_counts(self, client: Runloop) -> None:
+ agent = client.agents.devbox_counts()
+ assert_matches_type(AgentDevboxCountsView, agent, path=["response"])
+
+ @parametrize
+ def test_raw_response_devbox_counts(self, client: Runloop) -> None:
+ response = client.agents.with_raw_response.devbox_counts()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = response.parse()
+ assert_matches_type(AgentDevboxCountsView, agent, path=["response"])
+
+ @parametrize
+ def test_streaming_response_devbox_counts(self, client: Runloop) -> None:
+ with client.agents.with_streaming_response.devbox_counts() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = response.parse()
+ assert_matches_type(AgentDevboxCountsView, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_list_public(self, client: Runloop) -> None:
+ agent = client.agents.list_public()
+ assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"])
+
+ @parametrize
+ def test_method_list_public_with_all_params(self, client: Runloop) -> None:
+ agent = client.agents.list_public(
+ include_total_count=True,
+ limit=0,
+ name="name",
+ search="search",
+ starting_after="starting_after",
+ version="version",
+ )
+ assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"])
+
+ @parametrize
+ def test_raw_response_list_public(self, client: Runloop) -> None:
+ response = client.agents.with_raw_response.list_public()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = response.parse()
+ assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list_public(self, client: Runloop) -> None:
+ with client.agents.with_streaming_response.list_public() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = response.parse()
+ assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
class TestAsyncAgents:
parametrize = pytest.mark.parametrize(
@@ -303,3 +406,103 @@ async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None
assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"])
assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncRunloop) -> None:
+ agent = await async_client.agents.delete(
+ "id",
+ )
+ assert_matches_type(object, agent, path=["response"])
+
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncRunloop) -> None:
+ response = await async_client.agents.with_raw_response.delete(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = await response.parse()
+ assert_matches_type(object, agent, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncRunloop) -> None:
+ async with async_client.agents.with_streaming_response.delete(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = await response.parse()
+ assert_matches_type(object, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncRunloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.agents.with_raw_response.delete(
+ "",
+ )
+
+ @parametrize
+ async def test_method_devbox_counts(self, async_client: AsyncRunloop) -> None:
+ agent = await async_client.agents.devbox_counts()
+ assert_matches_type(AgentDevboxCountsView, agent, path=["response"])
+
+ @parametrize
+ async def test_raw_response_devbox_counts(self, async_client: AsyncRunloop) -> None:
+ response = await async_client.agents.with_raw_response.devbox_counts()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = await response.parse()
+ assert_matches_type(AgentDevboxCountsView, agent, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_devbox_counts(self, async_client: AsyncRunloop) -> None:
+ async with async_client.agents.with_streaming_response.devbox_counts() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = await response.parse()
+ assert_matches_type(AgentDevboxCountsView, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_list_public(self, async_client: AsyncRunloop) -> None:
+ agent = await async_client.agents.list_public()
+ assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"])
+
+ @parametrize
+ async def test_method_list_public_with_all_params(self, async_client: AsyncRunloop) -> None:
+ agent = await async_client.agents.list_public(
+ include_total_count=True,
+ limit=0,
+ name="name",
+ search="search",
+ starting_after="starting_after",
+ version="version",
+ )
+ assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list_public(self, async_client: AsyncRunloop) -> None:
+ response = await async_client.agents.with_raw_response.list_public()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = await response.parse()
+ assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list_public(self, async_client: AsyncRunloop) -> None:
+ async with async_client.agents.with_streaming_response.list_public() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = await response.parse()
+ assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
From 0d1d616637422d1a5762123f194e9aaef0639bf4 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 13 Apr 2026 23:29:32 +0000
Subject: [PATCH 5/5] release: 1.19.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 23 +++++++++++++++++++++++
pyproject.toml | 2 +-
src/runloop_api_client/_version.py | 2 +-
4 files changed, 26 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index aa06f8634..de44c40d8 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "1.18.1"
+ ".": "1.19.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e01266433..afa8cdb3c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,28 @@
# Changelog
+## 1.19.0 (2026-04-13)
+
+Full Changelog: [v1.18.1...v1.19.0](https://github.com/runloopai/api-client-python/compare/v1.18.1...v1.19.0)
+
+### Features
+
+* Add wake on axon event as an API primitive ([#8681](https://github.com/runloopai/api-client-python/issues/8681)) ([255099a](https://github.com/runloopai/api-client-python/commit/255099a940a7892f3c66e6534517e3545d7daccb))
+* Allow API keys to create API and restricted keys ([#8663](https://github.com/runloopai/api-client-python/issues/8663)) ([2e03b55](https://github.com/runloopai/api-client-python/commit/2e03b55638a83a8354481f47a30910e94ff57fc3))
+
+
+### Bug Fixes
+
+* add missing agent API paths to stainless config ([#8699](https://github.com/runloopai/api-client-python/issues/8699)) ([4f6fe60](https://github.com/runloopai/api-client-python/commit/4f6fe60a63fd31c049e54febb81801031dce2325))
+* ensure file data are only sent as 1 parameter ([f7ca2cc](https://github.com/runloopai/api-client-python/commit/f7ca2cc280f1e7a4ab73f331be8fe3e33ae1c611))
+
+
+### Documentation
+
+* add snapshot, suspend & resume example ([#764](https://github.com/runloopai/api-client-python/issues/764)) ([f4b942b](https://github.com/runloopai/api-client-python/commit/f4b942b3329e98a4ea5182c04d6db90fd0b70308))
+* added Async vs Sync tabs, removed top-level class descriptions ([#783](https://github.com/runloopai/api-client-python/issues/783)) ([6a99c56](https://github.com/runloopai/api-client-python/commit/6a99c569914d93e07087ed5003ee7c77f2ab88ff))
+* restructure sphinx docs with async-first API reference and full type coverage ([#782](https://github.com/runloopai/api-client-python/issues/782)) ([514cf93](https://github.com/runloopai/api-client-python/commit/514cf93e8ff8b97bfde171a83cf9abf211193e7f))
+* show full field descriptions for SDK parameter types ([#784](https://github.com/runloopai/api-client-python/issues/784)) ([f04a506](https://github.com/runloopai/api-client-python/commit/f04a5069c33fceb7a4d3db634bfd77ab789314dc))
+
## 1.18.1 (2026-04-10)
Full Changelog: [v1.17.0...v1.18.1](https://github.com/runloopai/api-client-python/compare/v1.17.0...v1.18.1)
diff --git a/pyproject.toml b/pyproject.toml
index 2c3dbdb53..64ca9ee03 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "runloop_api_client"
-version = "1.18.1"
+version = "1.19.0"
description = "The official Python library for the runloop API"
dynamic = ["readme"]
license = "MIT"
diff --git a/src/runloop_api_client/_version.py b/src/runloop_api_client/_version.py
index 4ca4fa13d..63342c137 100644
--- a/src/runloop_api_client/_version.py
+++ b/src/runloop_api_client/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "runloop_api_client"
-__version__ = "1.18.1" # x-release-please-version
+__version__ = "1.19.0" # x-release-please-version