diff --git a/packages/js-sdk/src/api/schema.gen.ts b/packages/js-sdk/src/api/schema.gen.ts index d3c42e3ee2..d9dbeff861 100644 --- a/packages/js-sdk/src/api/schema.gen.ts +++ b/packages/js-sdk/src/api/schema.gen.ts @@ -287,6 +287,61 @@ export interface paths { patch?: never; trace?: never; }; + "/sandboxes/{sandboxID}/network": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** @description Update the network configuration for a running sandbox. Replaces the current egress rules with the provided configuration. Omitting both fields clears all egress rules. */ + put: { + parameters: { + query?: never; + header?: never; + path: { + sandboxID: components["parameters"]["sandboxID"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Allow sandbox to access the internet. When set to false, it behaves the same as specifying denyOut to 0.0.0.0/0 in the network config. */ + allow_internet_access?: boolean; + /** @description List of allowed destinations for egress traffic. Each entry can be a CIDR block (e.g. "8.8.8.8/32"), a bare IP address (e.g. "8.8.8.8"), or a domain name (e.g. "example.com", "*.example.com"). Allowed entries always take precedence over denied entries. */ + allowOut?: string[]; + /** @description List of denied CIDR blocks or IP addresses for egress traffic. Domain names are not supported for deny rules. */ + denyOut?: string[]; + /** @description Per-domain transform rules. Replaces all existing rules when provided. */ + rules?: { + [key: string]: components["schemas"]["SandboxNetworkRule"][]; + }; + }; + }; + }; + responses: { + /** @description Successfully updated the sandbox network configuration */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 401: components["responses"]["401"]; + 404: components["responses"]["404"]; + 409: components["responses"]["409"]; + 500: components["responses"]["500"]; + }; + }; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/sandboxes/{sandboxID}/pause": { parameters: { query?: never; @@ -2092,10 +2147,13 @@ export interface components { memoryUsedBytes: number; }; /** - * @description Status of the node + * @description Status of the node. + * - draining: the node is bound to be shut down. It will not accept new sandboxes and will stop once all existing sandboxes are done. + * - standby: the node is not actively used, but it can return to ready and continue serving traffic. + * * @enum {string} */ - NodeStatus: "ready" | "draining" | "connecting" | "unhealthy"; + NodeStatus: "ready" | "draining" | "connecting" | "unhealthy" | "standby"; NodeStatusChange: { /** * Format: uuid @@ -2256,6 +2314,11 @@ export interface components { * @description Disk used in bytes */ diskUsed: number; + /** + * Format: int64 + * @description Cached memory (page cache) in bytes + */ + memCache: number; /** * Format: int64 * @description Total memory in bytes @@ -2279,17 +2342,34 @@ export interface components { timestampUnix: number; }; SandboxNetworkConfig: { - /** @description List of allowed CIDR blocks or IP addresses for egress traffic. Allowed addresses always take precedence over blocked addresses. */ + /** @description List of allowed destinations for egress traffic. Each entry can be a CIDR block (e.g. "8.8.8.8/32"), a bare IP address (e.g. "8.8.8.8"), or a domain name (e.g. "example.com", "*.example.com"). Allowed entries always take precedence over denied entries. */ allowOut?: string[]; /** * @description Specify if the sandbox URLs should be accessible only with authentication. * @default true */ allowPublicTraffic?: boolean; - /** @description List of denied CIDR blocks or IP addresses for egress traffic */ + /** @description List of denied CIDR blocks or IP addresses for egress traffic. Domain names are not supported for deny rules. */ denyOut?: string[]; /** @description Specify host mask which will be used for all sandbox requests */ maskRequestHost?: string; + /** @description Per-domain transform rules applied to matching egress HTTP/HTTPS requests. Keys are domains (e.g. "api.example.com", "example.com"). A domain listed here is not automatically allowed - use allowOut to permit the traffic. + * */ + rules?: { + [key: string]: components["schemas"]["SandboxNetworkRule"][]; + }; + }; + /** @description Transform rule applied to egress requests matching a domain pattern. */ + SandboxNetworkRule: { + transform?: components["schemas"]["SandboxNetworkTransform"]; + }; + /** @description Transformations applied to matching egress requests before forwarding. */ + SandboxNetworkTransform: { + /** @description HTTP headers to inject or override in matching requests. An existing header with the same name is replaced. Values are plain strings; secret resolution happens client-side before sending to the API. + * */ + headers?: { + [key: string]: string; + }; }; /** * @description Action taken when the sandbox times out. diff --git a/packages/js-sdk/src/index.ts b/packages/js-sdk/src/index.ts index e67092a01c..7f3a80dcf7 100644 --- a/packages/js-sdk/src/index.ts +++ b/packages/js-sdk/src/index.ts @@ -58,6 +58,13 @@ export type { SandboxListOpts, SandboxPaginator, SandboxNetworkOpts, + SandboxNetworkInfo, + SandboxNetworkSelector, + SandboxNetworkSelectorContext, + SandboxNetworkRule, + SandboxNetworkRules, + SandboxNetworkTransform, + SandboxNetworkTransformContext, SandboxLifecycle, SandboxInfoLifecycle, SnapshotInfo, diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 6fc03addb7..21c2962a5f 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -5,6 +5,7 @@ import { DEFAULT_SANDBOX_TIMEOUT_MS, } from '../connectionConfig' import { compareVersions } from 'compare-versions' +import { ALL_TRAFFIC } from './network' import { SandboxNotFoundError, TemplateError } from '../errors' import { timeoutToSeconds } from '../utils' import type { Volume } from '../volume' @@ -33,23 +34,132 @@ export type GitHubMcpServer = { } } +/** + * Transform applied to egress requests matching a {@link SandboxNetworkRule}. + */ +export type SandboxNetworkTransform = { + /** + * Headers to inject into the outbound request. Values override any headers + * already present on the request. + */ + headers?: Record +} + +/** + * Context passed to a {@link SandboxNetworkRule} `transform` callback. Each + * field is a literal placeholder string (e.g. `'${e2b.sandboxId}'`) that the + * proxy resolves per request at egress time. + */ +export type SandboxNetworkTransformContext = { + /** Placeholder `'${e2b.sandboxId}'`. */ + sandboxId: string + /** Placeholder `'${e2b.teamId}'`. */ + teamId: string + /** Placeholder `'${e2b.executionId}'`. */ + executionId: string + /** Identity-related placeholders. */ + identity: { + /** Placeholder `'${e2b.identity.jwt}'`. */ + jwt: string + } +} + +/** + * Per-domain rule applied to egress requests. + */ +export type SandboxNetworkRule = { + /** + * Transform applied to requests matching this rule. Accepts either a static + * object or a callback that receives a {@link SandboxNetworkTransformContext} + * of placeholder strings; the resolved object is sent to the API as-is. + */ + transform?: + | SandboxNetworkTransform + | ((ctx: SandboxNetworkTransformContext) => SandboxNetworkTransform) +} + +/** + * Map of host (or CIDR / IP) to ordered list of rules applied to outbound + * requests for that host. Accepts either a plain object or a `Map`. + * Registering a host here does not allow egress on its own — the host must + * also appear in {@link SandboxNetworkOpts.allowOut}. + */ +export type SandboxNetworkRules = + | Record + | Map + +/** + * Context passed to {@link SandboxNetworkOpts.allowOut} and + * {@link SandboxNetworkOpts.denyOut} when they are defined as functions. + */ +export type SandboxNetworkSelectorContext = { + /** All traffic sentinel — equivalent to `'0.0.0.0/0'`. */ + allTraffic: string + /** Rules registered in {@link SandboxNetworkOpts.rules}. */ + rules: Map +} + +/** + * Egress rule list, either a static array of CIDR blocks / IP addresses / + * hostnames, or a callback that receives `{ allTraffic, rules }` and returns + * the same. + */ +export type SandboxNetworkSelector = + | string[] + | ((ctx: SandboxNetworkSelectorContext) => string[]) + export type SandboxNetworkOpts = { /** * Allow outbound traffic from the sandbox to the specified addresses. * If `allowOut` is not specified, all outbound traffic is allowed. * + * Accepts either a static array of CIDR blocks, IP addresses, or hostnames, + * or a callback that receives `{ allTraffic, rules }` and returns the same. + * `allTraffic` is `'0.0.0.0/0'`; `rules` is a `Map` view of + * {@link SandboxNetworkOpts.rules}. + * * Examples: - * - To allow traffic to a specific addresses: `["1.1.1.1", "8.8.8.0/24"]` + * - Static list: `["1.1.1.1", "8.8.8.0/24"]` + * - Allow only rule-registered hosts: + * `({ rules }) => [...rules.keys()]` */ - allowOut?: string[] + allowOut?: SandboxNetworkSelector /** * Deny outbound traffic from the sandbox to the specified addresses. * + * Accepts the same shapes as {@link allowOut}. + * * Examples: - * - To deny traffic to a specific addresses: `["1.1.1.1", "8.8.8.0/24"]` + * - Static list: `["1.1.1.1", "8.8.8.0/24"]` + * - Block all egress: `({ allTraffic }) => [allTraffic]` */ - denyOut?: string[] + denyOut?: SandboxNetworkSelector + + /** + * Per-domain transform rules applied to matching egress HTTP/HTTPS + * requests. Keys are domains (e.g. `"api.example.com"`); values are + * ordered lists of rules. + * + * Registering a host here does not allow egress on its own — the host must + * also appear in {@link allowOut}. Hosts registered here are exposed to the + * `allowOut`/`denyOut` callbacks via `rules`. + * + * @example + * ```ts + * await Sandbox.create({ + * network: { + * allowOut: ({ rules }) => [...rules.keys()], + * rules: { + * 'api.openai.com': [ + * { transform: { headers: { Authorization: `Bearer ${token}` } } }, + * ], + * }, + * }, + * }) + * ``` + */ + rules?: SandboxNetworkRules /** * Specify if the sandbox URLs should be accessible only with authentication. @@ -65,6 +175,19 @@ export type SandboxNetworkOpts = { maskRequestHost?: string } +/** + * Network configuration as returned by the sandbox info endpoint. Mirrors + * {@link SandboxNetworkOpts} but with `allowOut`/`denyOut` always materialized + * to plain string arrays. + */ +export type SandboxNetworkInfo = { + allowOut?: string[] + denyOut?: string[] + rules?: SandboxNetworkRules + allowPublicTraffic?: boolean + maskRequestHost?: string +} + export type SandboxLifecycle = { /** * Action to take when sandbox timeout is reached. @@ -349,7 +472,7 @@ export interface SandboxInfo { /** * Sandbox network configuration. */ - network?: SandboxNetworkOpts + network?: SandboxNetworkInfo /** * Sandbox lifecycle configuration. @@ -402,6 +525,76 @@ export interface SandboxMetrics { diskTotal: number } +function resolveNetworkSelector( + selector: SandboxNetworkSelector | undefined, + rules: Map +): string[] | undefined { + if (selector === undefined) { + return undefined + } + + if (typeof selector === 'function') { + return selector({ allTraffic: ALL_TRAFFIC, rules }) + } + + return selector +} + +const TRANSFORM_CONTEXT: SandboxNetworkTransformContext = { + sandboxId: '${e2b.sandboxId}', + teamId: '${e2b.teamId}', + executionId: '${e2b.executionId}', + identity: { + jwt: '${e2b.identity.jwt}', + }, +} + +function resolveRulesForBody( + rules: Map +): Record { + const out: Record = {} + for (const [host, hostRules] of rules) { + out[host] = hostRules.map((rule) => { + if (rule.transform === undefined) return {} + const transform = + typeof rule.transform === 'function' + ? rule.transform(TRANSFORM_CONTEXT) + : rule.transform + return { transform } + }) + } + return out +} + +function buildNetworkBody( + network: SandboxNetworkOpts | undefined +): components['schemas']['SandboxNetworkConfig'] | undefined { + if (!network) { + return undefined + } + + const rules = + network.rules instanceof Map + ? network.rules + : new Map(Object.entries(network.rules ?? {})) + const allowOut = resolveNetworkSelector(network.allowOut, rules) + const denyOut = resolveNetworkSelector(network.denyOut, rules) + + return { + ...(allowOut !== undefined ? { allowOut } : {}), + ...(denyOut !== undefined ? { denyOut } : {}), + ...(network.rules !== undefined + ? { rules: resolveRulesForBody(rules) } + : {}), + ...(network.allowPublicTraffic !== undefined + ? { allowPublicTraffic: network.allowPublicTraffic } + : {}), + ...(network.maskRequestHost !== undefined + ? { maskRequestHost: network.maskRequestHost } + : {}), + } +} + function getLifecycle( opts?: Pick ): SandboxLifecycle { @@ -614,6 +807,7 @@ export class SandboxApi { ? { allowOut: res.data.network.allowOut, denyOut: res.data.network.denyOut, + rules: res.data.network.rules ?? undefined, allowPublicTraffic: res.data.network.allowPublicTraffic, maskRequestHost: res.data.network.maskRequestHost, } @@ -792,7 +986,7 @@ export class SandboxApi { timeout: timeoutToSeconds(timeoutMs), secure: opts?.secure ?? true, allow_internet_access: opts?.allowInternetAccess ?? true, - network: opts?.network, + network: buildNetworkBody(opts?.network), ...(autoPause !== undefined ? { autoPause } : {}), ...(autoResumeEnabled !== undefined ? { autoResume: { enabled: autoResumeEnabled } } diff --git a/packages/js-sdk/tests/sandbox/network.test.ts b/packages/js-sdk/tests/sandbox/network.test.ts index b6d2ebe23c..31b4806d04 100644 --- a/packages/js-sdk/tests/sandbox/network.test.ts +++ b/packages/js-sdk/tests/sandbox/network.test.ts @@ -1,13 +1,13 @@ import { assert, expect, describe } from 'vitest' -import { CommandExitError, ALL_TRAFFIC } from '../../src' +import { CommandExitError } from '../../src' import { sandboxTest, isDebug } from '../setup.js' describe('allow only 1.1.1.1', () => { sandboxTest.scoped({ sandboxOpts: { network: { - denyOut: [ALL_TRAFFIC], + denyOut: ({ allTraffic }) => [allTraffic], allowOut: ['1.1.1.1'], }, }, @@ -62,17 +62,17 @@ describe('deny specific IP address', () => { ) }) -describe('deny all traffic using allTraffic helper', () => { +describe('deny all traffic using allTraffic selector', () => { sandboxTest.scoped({ sandboxOpts: { network: { - denyOut: [ALL_TRAFFIC], + denyOut: ({ allTraffic }) => [allTraffic], }, }, }) sandboxTest.skipIf(isDebug)( - 'deny all traffic using allTraffic helper', + 'deny all traffic using allTraffic selector', async ({ sandbox }) => { // Test that all traffic is denied await expect( @@ -94,7 +94,7 @@ describe('allow takes precedence over deny', () => { sandboxTest.scoped({ sandboxOpts: { network: { - denyOut: [ALL_TRAFFIC], + denyOut: ({ allTraffic }) => [allTraffic], allowOut: ['1.1.1.1', '8.8.8.8'], }, }, @@ -193,6 +193,50 @@ describe('allowPublicTraffic=true', () => { ) }) +describe('firewall transform injects headers', () => { + const injectedHeader = 'X-E2B-Test-Token' + const injectedValue = 'e2b-transform-value-123' + + sandboxTest.scoped({ + sandboxOpts: { + network: { + allowOut: ({ rules }) => [...rules.keys()], + rules: { + 'httpbin.org': [ + { + transform: { + headers: { + [injectedHeader]: injectedValue, + }, + }, + }, + ], + }, + }, + }, + }) + + sandboxTest.skipIf(isDebug)( + 'injected header is reflected by httpbin.org/headers', + async ({ sandbox }) => { + const result = await sandbox.commands.run( + 'curl -sS --max-time 10 https://httpbin.org/headers' + ) + assert.equal(result.exitCode, 0) + + const parsed = JSON.parse(result.stdout) as { + headers: Record + } + const reflected = parsed.headers[injectedHeader] + assert.equal( + reflected, + injectedValue, + `expected httpbin to reflect ${injectedHeader}=${injectedValue}, got headers: ${JSON.stringify(parsed.headers)}` + ) + } + ) +}) + describe('maskRequestHost option', () => { sandboxTest.scoped({ sandboxOpts: { diff --git a/packages/python-sdk/e2b/__init__.py b/packages/python-sdk/e2b/__init__.py index e43f621e41..75abda7a1b 100644 --- a/packages/python-sdk/e2b/__init__.py +++ b/packages/python-sdk/e2b/__init__.py @@ -79,7 +79,14 @@ SandboxInfoLifecycle, SandboxMetrics, SandboxLifecycle, + SandboxNetworkInfo, SandboxNetworkOpts, + SandboxNetworkRule, + SandboxNetworkRules, + SandboxNetworkSelector, + SandboxNetworkSelectorContext, + SandboxNetworkTransform, + SandboxNetworkTransformContext, SandboxQuery, SandboxState, SnapshotInfo, @@ -183,6 +190,13 @@ "FileType", # Network "SandboxNetworkOpts", + "SandboxNetworkInfo", + "SandboxNetworkSelector", + "SandboxNetworkSelectorContext", + "SandboxNetworkRule", + "SandboxNetworkRules", + "SandboxNetworkTransform", + "SandboxNetworkTransformContext", "SandboxLifecycle", "ALL_TRAFFIC", # Snapshot diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/put_sandboxes_sandbox_id_network.py b/packages/python-sdk/e2b/api/client/api/sandboxes/put_sandboxes_sandbox_id_network.py new file mode 100644 index 0000000000..cf6be790d6 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/put_sandboxes_sandbox_id_network.py @@ -0,0 +1,193 @@ +from http import HTTPStatus +from typing import Any, Optional, Union, cast + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.error import Error +from ...models.put_sandboxes_sandbox_id_network_body import ( + PutSandboxesSandboxIDNetworkBody, +) +from ...types import Response + + +def _get_kwargs( + sandbox_id: str, + *, + body: PutSandboxesSandboxIDNetworkBody, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + + _kwargs: dict[str, Any] = { + "method": "put", + "url": f"/sandboxes/{sandbox_id}/network", + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Union[Any, Error]]: + if response.status_code == 204: + response_204 = cast(Any, None) + return response_204 + if response.status_code == 401: + response_401 = Error.from_dict(response.json()) + + return response_401 + if response.status_code == 404: + response_404 = Error.from_dict(response.json()) + + return response_404 + if response.status_code == 409: + response_409 = Error.from_dict(response.json()) + + return response_409 + if response.status_code == 500: + response_500 = Error.from_dict(response.json()) + + return response_500 + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[Union[Any, Error]]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + sandbox_id: str, + *, + client: AuthenticatedClient, + body: PutSandboxesSandboxIDNetworkBody, +) -> Response[Union[Any, Error]]: + """Update the network configuration for a running sandbox. Replaces the current egress rules with the + provided configuration. Omitting both fields clears all egress rules. + + Args: + sandbox_id (str): + body (PutSandboxesSandboxIDNetworkBody): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Union[Any, Error]] + """ + + kwargs = _get_kwargs( + sandbox_id=sandbox_id, + body=body, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + sandbox_id: str, + *, + client: AuthenticatedClient, + body: PutSandboxesSandboxIDNetworkBody, +) -> Optional[Union[Any, Error]]: + """Update the network configuration for a running sandbox. Replaces the current egress rules with the + provided configuration. Omitting both fields clears all egress rules. + + Args: + sandbox_id (str): + body (PutSandboxesSandboxIDNetworkBody): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Union[Any, Error] + """ + + return sync_detailed( + sandbox_id=sandbox_id, + client=client, + body=body, + ).parsed + + +async def asyncio_detailed( + sandbox_id: str, + *, + client: AuthenticatedClient, + body: PutSandboxesSandboxIDNetworkBody, +) -> Response[Union[Any, Error]]: + """Update the network configuration for a running sandbox. Replaces the current egress rules with the + provided configuration. Omitting both fields clears all egress rules. + + Args: + sandbox_id (str): + body (PutSandboxesSandboxIDNetworkBody): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Union[Any, Error]] + """ + + kwargs = _get_kwargs( + sandbox_id=sandbox_id, + body=body, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + sandbox_id: str, + *, + client: AuthenticatedClient, + body: PutSandboxesSandboxIDNetworkBody, +) -> Optional[Union[Any, Error]]: + """Update the network configuration for a running sandbox. Replaces the current egress rules with the + provided configuration. Omitting both fields clears all egress rules. + + Args: + sandbox_id (str): + body (PutSandboxesSandboxIDNetworkBody): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Union[Any, Error] + """ + + return ( + await asyncio_detailed( + sandbox_id=sandbox_id, + client=client, + body=body, + ) + ).parsed diff --git a/packages/python-sdk/e2b/api/client/models/__init__.py b/packages/python-sdk/e2b/api/client/models/__init__.py index fb263334ff..365bfe9a29 100644 --- a/packages/python-sdk/e2b/api/client/models/__init__.py +++ b/packages/python-sdk/e2b/api/client/models/__init__.py @@ -42,6 +42,10 @@ PostSandboxesSandboxIDSnapshotsBody, ) from .post_sandboxes_sandbox_id_timeout_body import PostSandboxesSandboxIDTimeoutBody +from .put_sandboxes_sandbox_id_network_body import PutSandboxesSandboxIDNetworkBody +from .put_sandboxes_sandbox_id_network_body_rules import ( + PutSandboxesSandboxIDNetworkBodyRules, +) from .resumed_sandbox import ResumedSandbox from .sandbox import Sandbox from .sandbox_auto_resume_config import SandboxAutoResumeConfig @@ -54,6 +58,10 @@ from .sandbox_logs_v2_response import SandboxLogsV2Response from .sandbox_metric import SandboxMetric from .sandbox_network_config import SandboxNetworkConfig +from .sandbox_network_config_rules import SandboxNetworkConfigRules +from .sandbox_network_rule import SandboxNetworkRule +from .sandbox_network_transform import SandboxNetworkTransform +from .sandbox_network_transform_headers import SandboxNetworkTransformHeaders from .sandbox_on_timeout import SandboxOnTimeout from .sandbox_state import SandboxState from .sandbox_volume_mount import SandboxVolumeMount @@ -125,6 +133,8 @@ "PostSandboxesSandboxIDRefreshesBody", "PostSandboxesSandboxIDSnapshotsBody", "PostSandboxesSandboxIDTimeoutBody", + "PutSandboxesSandboxIDNetworkBody", + "PutSandboxesSandboxIDNetworkBodyRules", "ResumedSandbox", "Sandbox", "SandboxAutoResumeConfig", @@ -138,6 +148,10 @@ "SandboxLogsV2Response", "SandboxMetric", "SandboxNetworkConfig", + "SandboxNetworkConfigRules", + "SandboxNetworkRule", + "SandboxNetworkTransform", + "SandboxNetworkTransformHeaders", "SandboxOnTimeout", "SandboxState", "SandboxVolumeMount", diff --git a/packages/python-sdk/e2b/api/client/models/node.py b/packages/python-sdk/e2b/api/client/models/node.py index fc60b7fc20..f7d2a47f6d 100644 --- a/packages/python-sdk/e2b/api/client/models/node.py +++ b/packages/python-sdk/e2b/api/client/models/node.py @@ -28,7 +28,10 @@ class Node: sandbox_count (int): Number of sandboxes running on the node sandbox_starting_count (int): Number of starting Sandboxes service_instance_id (str): Service instance identifier of the node - status (NodeStatus): Status of the node + status (NodeStatus): Status of the node. + - draining: the node is bound to be shut down. It will not accept new sandboxes and will stop once all existing + sandboxes are done. + - standby: the node is not actively used, but it can return to ready and continue serving traffic. version (str): Version of the orchestrator """ diff --git a/packages/python-sdk/e2b/api/client/models/node_detail.py b/packages/python-sdk/e2b/api/client/models/node_detail.py index 03c7362803..a5c1f61d3d 100644 --- a/packages/python-sdk/e2b/api/client/models/node_detail.py +++ b/packages/python-sdk/e2b/api/client/models/node_detail.py @@ -28,7 +28,10 @@ class NodeDetail: metrics (NodeMetrics): Node metrics sandbox_count (int): Number of sandboxes running on the node service_instance_id (str): Service instance identifier of the node - status (NodeStatus): Status of the node + status (NodeStatus): Status of the node. + - draining: the node is bound to be shut down. It will not accept new sandboxes and will stop once all existing + sandboxes are done. + - standby: the node is not actively used, but it can return to ready and continue serving traffic. version (str): Version of the orchestrator """ diff --git a/packages/python-sdk/e2b/api/client/models/node_status.py b/packages/python-sdk/e2b/api/client/models/node_status.py index 4529e3b542..9f7aa1f1b1 100644 --- a/packages/python-sdk/e2b/api/client/models/node_status.py +++ b/packages/python-sdk/e2b/api/client/models/node_status.py @@ -5,6 +5,7 @@ class NodeStatus(str, Enum): CONNECTING = "connecting" DRAINING = "draining" READY = "ready" + STANDBY = "standby" UNHEALTHY = "unhealthy" def __str__(self) -> str: diff --git a/packages/python-sdk/e2b/api/client/models/node_status_change.py b/packages/python-sdk/e2b/api/client/models/node_status_change.py index b628e429c6..06d09a1bb1 100644 --- a/packages/python-sdk/e2b/api/client/models/node_status_change.py +++ b/packages/python-sdk/e2b/api/client/models/node_status_change.py @@ -15,7 +15,10 @@ class NodeStatusChange: """ Attributes: - status (NodeStatus): Status of the node + status (NodeStatus): Status of the node. + - draining: the node is bound to be shut down. It will not accept new sandboxes and will stop once all existing + sandboxes are done. + - standby: the node is not actively used, but it can return to ready and continue serving traffic. cluster_id (Union[Unset, UUID]): Identifier of the cluster """ diff --git a/packages/python-sdk/e2b/api/client/models/put_sandboxes_sandbox_id_network_body.py b/packages/python-sdk/e2b/api/client/models/put_sandboxes_sandbox_id_network_body.py new file mode 100644 index 0000000000..c37a89368a --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/put_sandboxes_sandbox_id_network_body.py @@ -0,0 +1,112 @@ +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, Union, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.put_sandboxes_sandbox_id_network_body_rules import ( + PutSandboxesSandboxIDNetworkBodyRules, + ) + + +T = TypeVar("T", bound="PutSandboxesSandboxIDNetworkBody") + + +@_attrs_define +class PutSandboxesSandboxIDNetworkBody: + """ + Attributes: + allow_out (Union[Unset, list[str]]): List of allowed destinations for egress traffic. Each entry can be a CIDR + block (e.g. "8.8.8.8/32"), a bare IP address (e.g. "8.8.8.8"), or a domain name (e.g. "example.com", + "*.example.com"). Allowed entries always take precedence over denied entries. + allow_internet_access (Union[Unset, bool]): Allow sandbox to access the internet. When set to false, it behaves + the same as specifying denyOut to 0.0.0.0/0 in the network config. + deny_out (Union[Unset, list[str]]): List of denied CIDR blocks or IP addresses for egress traffic. Domain names + are not supported for deny rules. + rules (Union[Unset, PutSandboxesSandboxIDNetworkBodyRules]): Per-domain transform rules. Replaces all existing + rules when provided. + """ + + allow_out: Union[Unset, list[str]] = UNSET + allow_internet_access: Union[Unset, bool] = UNSET + deny_out: Union[Unset, list[str]] = UNSET + rules: Union[Unset, "PutSandboxesSandboxIDNetworkBodyRules"] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + allow_out: Union[Unset, list[str]] = UNSET + if not isinstance(self.allow_out, Unset): + allow_out = self.allow_out + + allow_internet_access = self.allow_internet_access + + deny_out: Union[Unset, list[str]] = UNSET + if not isinstance(self.deny_out, Unset): + deny_out = self.deny_out + + rules: Union[Unset, dict[str, Any]] = UNSET + if not isinstance(self.rules, Unset): + rules = self.rules.to_dict() + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if allow_out is not UNSET: + field_dict["allowOut"] = allow_out + if allow_internet_access is not UNSET: + field_dict["allow_internet_access"] = allow_internet_access + if deny_out is not UNSET: + field_dict["denyOut"] = deny_out + if rules is not UNSET: + field_dict["rules"] = rules + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.put_sandboxes_sandbox_id_network_body_rules import ( + PutSandboxesSandboxIDNetworkBodyRules, + ) + + d = dict(src_dict) + allow_out = cast(list[str], d.pop("allowOut", UNSET)) + + allow_internet_access = d.pop("allow_internet_access", UNSET) + + deny_out = cast(list[str], d.pop("denyOut", UNSET)) + + _rules = d.pop("rules", UNSET) + rules: Union[Unset, PutSandboxesSandboxIDNetworkBodyRules] + if isinstance(_rules, Unset): + rules = UNSET + else: + rules = PutSandboxesSandboxIDNetworkBodyRules.from_dict(_rules) + + put_sandboxes_sandbox_id_network_body = cls( + allow_out=allow_out, + allow_internet_access=allow_internet_access, + deny_out=deny_out, + rules=rules, + ) + + put_sandboxes_sandbox_id_network_body.additional_properties = d + return put_sandboxes_sandbox_id_network_body + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/put_sandboxes_sandbox_id_network_body_rules.py b/packages/python-sdk/e2b/api/client/models/put_sandboxes_sandbox_id_network_body_rules.py new file mode 100644 index 0000000000..1af75b3cb3 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/put_sandboxes_sandbox_id_network_body_rules.py @@ -0,0 +1,71 @@ +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +if TYPE_CHECKING: + from ..models.sandbox_network_rule import SandboxNetworkRule + + +T = TypeVar("T", bound="PutSandboxesSandboxIDNetworkBodyRules") + + +@_attrs_define +class PutSandboxesSandboxIDNetworkBodyRules: + """Per-domain transform rules. Replaces all existing rules when provided.""" + + additional_properties: dict[str, list["SandboxNetworkRule"]] = _attrs_field( + init=False, factory=dict + ) + + def to_dict(self) -> dict[str, Any]: + field_dict: dict[str, Any] = {} + for prop_name, prop in self.additional_properties.items(): + field_dict[prop_name] = [] + for additional_property_item_data in prop: + additional_property_item = additional_property_item_data.to_dict() + field_dict[prop_name].append(additional_property_item) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.sandbox_network_rule import SandboxNetworkRule + + d = dict(src_dict) + put_sandboxes_sandbox_id_network_body_rules = cls() + + additional_properties = {} + for prop_name, prop_dict in d.items(): + additional_property = [] + _additional_property = prop_dict + for additional_property_item_data in _additional_property: + additional_property_item = SandboxNetworkRule.from_dict( + additional_property_item_data + ) + + additional_property.append(additional_property_item) + + additional_properties[prop_name] = additional_property + + put_sandboxes_sandbox_id_network_body_rules.additional_properties = ( + additional_properties + ) + return put_sandboxes_sandbox_id_network_body_rules + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> list["SandboxNetworkRule"]: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: list["SandboxNetworkRule"]) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/sandbox_metric.py b/packages/python-sdk/e2b/api/client/models/sandbox_metric.py index eb7442fd9e..cbfb9eeb76 100644 --- a/packages/python-sdk/e2b/api/client/models/sandbox_metric.py +++ b/packages/python-sdk/e2b/api/client/models/sandbox_metric.py @@ -18,6 +18,7 @@ class SandboxMetric: cpu_used_pct (float): CPU usage percentage disk_total (int): Total disk space in bytes disk_used (int): Disk used in bytes + mem_cache (int): Cached memory (page cache) in bytes mem_total (int): Total memory in bytes mem_used (int): Memory used in bytes timestamp (datetime.datetime): Timestamp of the metric entry @@ -28,6 +29,7 @@ class SandboxMetric: cpu_used_pct: float disk_total: int disk_used: int + mem_cache: int mem_total: int mem_used: int timestamp: datetime.datetime @@ -43,6 +45,8 @@ def to_dict(self) -> dict[str, Any]: disk_used = self.disk_used + mem_cache = self.mem_cache + mem_total = self.mem_total mem_used = self.mem_used @@ -59,6 +63,7 @@ def to_dict(self) -> dict[str, Any]: "cpuUsedPct": cpu_used_pct, "diskTotal": disk_total, "diskUsed": disk_used, + "memCache": mem_cache, "memTotal": mem_total, "memUsed": mem_used, "timestamp": timestamp, @@ -79,6 +84,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: disk_used = d.pop("diskUsed") + mem_cache = d.pop("memCache") + mem_total = d.pop("memTotal") mem_used = d.pop("memUsed") @@ -92,6 +99,7 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: cpu_used_pct=cpu_used_pct, disk_total=disk_total, disk_used=disk_used, + mem_cache=mem_cache, mem_total=mem_total, mem_used=mem_used, timestamp=timestamp, diff --git a/packages/python-sdk/e2b/api/client/models/sandbox_network_config.py b/packages/python-sdk/e2b/api/client/models/sandbox_network_config.py index 08284c792f..c7a99c127b 100644 --- a/packages/python-sdk/e2b/api/client/models/sandbox_network_config.py +++ b/packages/python-sdk/e2b/api/client/models/sandbox_network_config.py @@ -1,11 +1,15 @@ from collections.abc import Mapping -from typing import Any, TypeVar, Union, cast +from typing import TYPE_CHECKING, Any, TypeVar, Union, cast from attrs import define as _attrs_define from attrs import field as _attrs_field from ..types import UNSET, Unset +if TYPE_CHECKING: + from ..models.sandbox_network_config_rules import SandboxNetworkConfigRules + + T = TypeVar("T", bound="SandboxNetworkConfig") @@ -13,18 +17,24 @@ class SandboxNetworkConfig: """ Attributes: - allow_out (Union[Unset, list[str]]): List of allowed CIDR blocks or IP addresses for egress traffic. Allowed - addresses always take precedence over blocked addresses. + allow_out (Union[Unset, list[str]]): List of allowed destinations for egress traffic. Each entry can be a CIDR + block (e.g. "8.8.8.8/32"), a bare IP address (e.g. "8.8.8.8"), or a domain name (e.g. "example.com", + "*.example.com"). Allowed entries always take precedence over denied entries. allow_public_traffic (Union[Unset, bool]): Specify if the sandbox URLs should be accessible only with authentication. Default: True. - deny_out (Union[Unset, list[str]]): List of denied CIDR blocks or IP addresses for egress traffic + deny_out (Union[Unset, list[str]]): List of denied CIDR blocks or IP addresses for egress traffic. Domain names + are not supported for deny rules. mask_request_host (Union[Unset, str]): Specify host mask which will be used for all sandbox requests + rules (Union[Unset, SandboxNetworkConfigRules]): Per-domain transform rules applied to matching egress + HTTP/HTTPS requests. Keys are domains (e.g. "api.example.com", "example.com"). A domain listed here is not + automatically allowed - use allowOut to permit the traffic. """ allow_out: Union[Unset, list[str]] = UNSET allow_public_traffic: Union[Unset, bool] = True deny_out: Union[Unset, list[str]] = UNSET mask_request_host: Union[Unset, str] = UNSET + rules: Union[Unset, "SandboxNetworkConfigRules"] = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -40,6 +50,10 @@ def to_dict(self) -> dict[str, Any]: mask_request_host = self.mask_request_host + rules: Union[Unset, dict[str, Any]] = UNSET + if not isinstance(self.rules, Unset): + rules = self.rules.to_dict() + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) @@ -51,11 +65,15 @@ def to_dict(self) -> dict[str, Any]: field_dict["denyOut"] = deny_out if mask_request_host is not UNSET: field_dict["maskRequestHost"] = mask_request_host + if rules is not UNSET: + field_dict["rules"] = rules return field_dict @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.sandbox_network_config_rules import SandboxNetworkConfigRules + d = dict(src_dict) allow_out = cast(list[str], d.pop("allowOut", UNSET)) @@ -65,11 +83,19 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: mask_request_host = d.pop("maskRequestHost", UNSET) + _rules = d.pop("rules", UNSET) + rules: Union[Unset, SandboxNetworkConfigRules] + if isinstance(_rules, Unset): + rules = UNSET + else: + rules = SandboxNetworkConfigRules.from_dict(_rules) + sandbox_network_config = cls( allow_out=allow_out, allow_public_traffic=allow_public_traffic, deny_out=deny_out, mask_request_host=mask_request_host, + rules=rules, ) sandbox_network_config.additional_properties = d diff --git a/packages/python-sdk/e2b/api/client/models/sandbox_network_config_rules.py b/packages/python-sdk/e2b/api/client/models/sandbox_network_config_rules.py new file mode 100644 index 0000000000..aeece3851b --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/sandbox_network_config_rules.py @@ -0,0 +1,72 @@ +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +if TYPE_CHECKING: + from ..models.sandbox_network_rule import SandboxNetworkRule + + +T = TypeVar("T", bound="SandboxNetworkConfigRules") + + +@_attrs_define +class SandboxNetworkConfigRules: + """Per-domain transform rules applied to matching egress HTTP/HTTPS requests. Keys are domains (e.g. "api.example.com", + "example.com"). A domain listed here is not automatically allowed - use allowOut to permit the traffic. + + """ + + additional_properties: dict[str, list["SandboxNetworkRule"]] = _attrs_field( + init=False, factory=dict + ) + + def to_dict(self) -> dict[str, Any]: + field_dict: dict[str, Any] = {} + for prop_name, prop in self.additional_properties.items(): + field_dict[prop_name] = [] + for additional_property_item_data in prop: + additional_property_item = additional_property_item_data.to_dict() + field_dict[prop_name].append(additional_property_item) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.sandbox_network_rule import SandboxNetworkRule + + d = dict(src_dict) + sandbox_network_config_rules = cls() + + additional_properties = {} + for prop_name, prop_dict in d.items(): + additional_property = [] + _additional_property = prop_dict + for additional_property_item_data in _additional_property: + additional_property_item = SandboxNetworkRule.from_dict( + additional_property_item_data + ) + + additional_property.append(additional_property_item) + + additional_properties[prop_name] = additional_property + + sandbox_network_config_rules.additional_properties = additional_properties + return sandbox_network_config_rules + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> list["SandboxNetworkRule"]: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: list["SandboxNetworkRule"]) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/sandbox_network_rule.py b/packages/python-sdk/e2b/api/client/models/sandbox_network_rule.py new file mode 100644 index 0000000000..f1d0a306ec --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/sandbox_network_rule.py @@ -0,0 +1,74 @@ +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.sandbox_network_transform import SandboxNetworkTransform + + +T = TypeVar("T", bound="SandboxNetworkRule") + + +@_attrs_define +class SandboxNetworkRule: + """Transform rule applied to egress requests matching a domain pattern. + + Attributes: + transform (Union[Unset, SandboxNetworkTransform]): Transformations applied to matching egress requests before + forwarding. + """ + + transform: Union[Unset, "SandboxNetworkTransform"] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + transform: Union[Unset, dict[str, Any]] = UNSET + if not isinstance(self.transform, Unset): + transform = self.transform.to_dict() + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if transform is not UNSET: + field_dict["transform"] = transform + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.sandbox_network_transform import SandboxNetworkTransform + + d = dict(src_dict) + _transform = d.pop("transform", UNSET) + transform: Union[Unset, SandboxNetworkTransform] + if isinstance(_transform, Unset): + transform = UNSET + else: + transform = SandboxNetworkTransform.from_dict(_transform) + + sandbox_network_rule = cls( + transform=transform, + ) + + sandbox_network_rule.additional_properties = d + return sandbox_network_rule + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/sandbox_network_transform.py b/packages/python-sdk/e2b/api/client/models/sandbox_network_transform.py new file mode 100644 index 0000000000..d754240c60 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/sandbox_network_transform.py @@ -0,0 +1,79 @@ +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.sandbox_network_transform_headers import ( + SandboxNetworkTransformHeaders, + ) + + +T = TypeVar("T", bound="SandboxNetworkTransform") + + +@_attrs_define +class SandboxNetworkTransform: + """Transformations applied to matching egress requests before forwarding. + + Attributes: + headers (Union[Unset, SandboxNetworkTransformHeaders]): HTTP headers to inject or override in matching requests. + An existing header with the same name is replaced. Values are plain strings; secret resolution happens client- + side before sending to the API. + """ + + headers: Union[Unset, "SandboxNetworkTransformHeaders"] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + headers: Union[Unset, dict[str, Any]] = UNSET + if not isinstance(self.headers, Unset): + headers = self.headers.to_dict() + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if headers is not UNSET: + field_dict["headers"] = headers + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.sandbox_network_transform_headers import ( + SandboxNetworkTransformHeaders, + ) + + d = dict(src_dict) + _headers = d.pop("headers", UNSET) + headers: Union[Unset, SandboxNetworkTransformHeaders] + if isinstance(_headers, Unset): + headers = UNSET + else: + headers = SandboxNetworkTransformHeaders.from_dict(_headers) + + sandbox_network_transform = cls( + headers=headers, + ) + + sandbox_network_transform.additional_properties = d + return sandbox_network_transform + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/sandbox_network_transform_headers.py b/packages/python-sdk/e2b/api/client/models/sandbox_network_transform_headers.py new file mode 100644 index 0000000000..c2d7c1aaa2 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/sandbox_network_transform_headers.py @@ -0,0 +1,47 @@ +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="SandboxNetworkTransformHeaders") + + +@_attrs_define +class SandboxNetworkTransformHeaders: + """HTTP headers to inject or override in matching requests. An existing header with the same name is replaced. Values + are plain strings; secret resolution happens client-side before sending to the API. + + """ + + additional_properties: dict[str, str] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + sandbox_network_transform_headers = cls() + + sandbox_network_transform_headers.additional_properties = d + return sandbox_network_transform_headers + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> str: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: str) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/sandbox/sandbox_api.py b/packages/python-sdk/e2b/sandbox/sandbox_api.py index 96e4d46271..762ca35cb4 100644 --- a/packages/python-sdk/e2b/sandbox/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox/sandbox_api.py @@ -1,6 +1,17 @@ from dataclasses import dataclass, field from datetime import datetime -from typing import Any, Dict, List, Literal, Optional, TypedDict, Union, cast +from typing import ( + Any, + Callable, + Dict, + List, + Literal, + Mapping, + Optional, + TypedDict, + Union, + cast, +) from typing_extensions import NotRequired, Unpack @@ -9,12 +20,17 @@ ListedSandbox, SandboxDetail, SandboxLifecycle as ClientSandboxLifecycle, - SandboxNetworkConfig as ClientSandboxNetworkConfig, + SandboxNetworkConfig, + SandboxNetworkConfigRules, + SandboxNetworkRule as ClientSandboxNetworkRule, + SandboxNetworkTransform as ClientSandboxNetworkTransform, + SandboxNetworkTransformHeaders as ClientSandboxNetworkTransformHeaders, SandboxState, ) from e2b.api.client.types import Unset from e2b.connection_config import ApiParams from e2b.sandbox.mcp import McpServer as BaseMcpServer +from e2b.sandbox.network import ALL_TRAFFIC class GitHubMcpServerConfig(TypedDict): @@ -45,26 +61,141 @@ class GitHubMcpServerConfig(TypedDict): McpServer = Union[BaseMcpServer, GitHubMcpServer] +class SandboxNetworkTransform(TypedDict): + """ + Transform applied to egress requests matching a :class:`SandboxNetworkRule`. + """ + + headers: NotRequired[Dict[str, str]] + """ + Headers to inject into the outbound request. Values override any headers + already present on the request. + """ + + +@dataclass(frozen=True) +class _SandboxNetworkTransformIdentity: + """Identity-related placeholders.""" + + jwt: str + """Placeholder ``"${e2b.identity.jwt}"``.""" + + +@dataclass(frozen=True) +class SandboxNetworkTransformContext: + """ + Context passed to a :class:`SandboxNetworkRule` ``transform`` callable. + Each field is a literal placeholder string (e.g. ``"${e2b.sandbox_id}"``) + that the proxy resolves per request at egress time. + """ + + sandbox_id: str + """Placeholder ``"${e2b.sandboxId}"``.""" + + team_id: str + """Placeholder ``"${e2b.teamId}"``.""" + + execution_id: str + """Placeholder ``"${e2b.executionId}"``.""" + + identity: _SandboxNetworkTransformIdentity + """Identity-related placeholders.""" + + +SandboxNetworkTransformResolver = Callable[ + [SandboxNetworkTransformContext], SandboxNetworkTransform +] + + +class SandboxNetworkRule(TypedDict): + """ + Per-domain rule applied to egress requests. + """ + + transform: NotRequired[ + Union[SandboxNetworkTransform, SandboxNetworkTransformResolver] + ] + """ + Transform applied to requests matching this rule. Accepts either a static + :class:`SandboxNetworkTransform` or a callable that receives a + :class:`SandboxNetworkTransformContext` of placeholder strings; the + resolved object is sent to the API as-is. + """ + + +SandboxNetworkRules = Dict[str, List[SandboxNetworkRule]] +""" +Map of host (or CIDR / IP) to ordered list of rules applied to outbound +requests for that host. Registering a host here does not allow egress on its +own — the host must also appear in ``SandboxNetworkOpts.allow_out``. +""" + + +@dataclass(frozen=True) +class SandboxNetworkSelectorContext: + """ + Context passed to ``allow_out``/``deny_out`` callables. + """ + + all_traffic: str + """All traffic sentinel — equivalent to ``"0.0.0.0/0"``.""" + + rules: Mapping[str, List[SandboxNetworkRule]] + """Rules registered in :attr:`SandboxNetworkOpts.rules`.""" + + +SandboxNetworkSelector = Union[ + List[str], + Callable[[SandboxNetworkSelectorContext], List[str]], +] +""" +Egress rule list, either a static list of CIDR blocks / IP addresses / +hostnames, or a callable that receives a :class:`SandboxNetworkSelectorContext` +and returns the same. +""" + + class SandboxNetworkOpts(TypedDict): """ Sandbox network configuration options. """ - allow_out: NotRequired[List[str]] + allow_out: NotRequired[SandboxNetworkSelector] """ Allow outbound traffic from the sandbox to the specified addresses. - If `allow_out` is not specified, all outbound traffic is allowed. + If ``allow_out`` is not specified, all outbound traffic is allowed. + + Accepts either a static list of CIDR blocks / IP addresses / hostnames, or + a callable that receives a :class:`SandboxNetworkSelectorContext` and + returns the same. ``ctx.all_traffic`` is ``"0.0.0.0/0"``; ``ctx.rules`` is + a read-only view of :attr:`rules`. Examples: - - To allow traffic to specific addresses: `["1.1.1.1", "8.8.8.0/24"]` + - Static list: ``["1.1.1.1", "8.8.8.0/24"]`` + - Allow only rule-registered hosts: + ``lambda ctx: list(ctx.rules.keys())`` """ - deny_out: NotRequired[List[str]] + deny_out: NotRequired[SandboxNetworkSelector] """ Deny outbound traffic from the sandbox to the specified addresses. + Accepts the same shapes as ``allow_out``. + Examples: - - To deny traffic to specific addresses: `["1.1.1.1", "8.8.8.0/24"]` + - Static list: ``["1.1.1.1", "8.8.8.0/24"]`` + - Block all egress: ``lambda ctx: [ctx.all_traffic]`` + """ + + rules: NotRequired[SandboxNetworkRules] + """ + Per-domain transform rules applied to matching egress HTTP/HTTPS + requests. Keys are domains (e.g. ``"api.example.com"``); values are + ordered lists of :class:`SandboxNetworkRule`. + + Registering a host here does not allow egress on its own — the host must + also appear in ``allow_out``. Hosts registered here are exposed to the + ``allow_out``/``deny_out`` callables via ``ctx.rules``. """ allow_public_traffic: NotRequired[bool] @@ -83,6 +214,20 @@ class SandboxNetworkOpts(TypedDict): """ +class SandboxNetworkInfo(TypedDict, total=False): + """ + Network configuration as returned by the sandbox info endpoint. + Mirrors :class:`SandboxNetworkOpts` but with ``allow_out``/``deny_out`` + always materialized to plain string lists. + """ + + allow_out: List[str] + deny_out: List[str] + rules: SandboxNetworkRules + allow_public_traffic: bool + mask_request_host: str + + class SandboxLifecycle(TypedDict): """ Sandbox lifecycle configuration; defines post-timeout behavior and auto-resume settings. @@ -117,6 +262,79 @@ class SandboxInfoLifecycle(TypedDict): """ +def _resolve_network_selector( + selector: Optional[SandboxNetworkSelector], + rules: Mapping[str, List[SandboxNetworkRule]], +) -> Optional[List[str]]: + if selector is None: + return None + + if callable(selector): + ctx = SandboxNetworkSelectorContext(all_traffic=ALL_TRAFFIC, rules=rules) + return list(selector(ctx)) + + return list(selector) + + +_TRANSFORM_CONTEXT = SandboxNetworkTransformContext( + sandbox_id="${e2b.sandboxId}", + team_id="${e2b.teamId}", + execution_id="${e2b.executionId}", + identity=_SandboxNetworkTransformIdentity(jwt="${e2b.identity.jwt}"), +) + + +def _build_client_rules(rules: SandboxNetworkRules) -> SandboxNetworkConfigRules: + client_rules = SandboxNetworkConfigRules() + for host, host_rules in rules.items(): + converted: List[ClientSandboxNetworkRule] = [] + for rule in host_rules: + transform = rule.get("transform") + if transform is None: + converted.append(ClientSandboxNetworkRule()) + continue + if callable(transform): + transform = transform(_TRANSFORM_CONTEXT) + + client_transform = ClientSandboxNetworkTransform() + headers = transform.get("headers") + if headers: + client_headers = ClientSandboxNetworkTransformHeaders() + client_headers.additional_properties = dict(headers) + client_transform.headers = client_headers + + converted.append(ClientSandboxNetworkRule(transform=client_transform)) + client_rules.additional_properties[host] = converted + + return client_rules + + +def build_network_config( + network: Optional[SandboxNetworkOpts], +) -> Optional[Dict[str, Any]]: + """Resolve a :class:`SandboxNetworkOpts` into the dict the API expects.""" + if network is None: + return None + + rules = network.get("rules") or {} + allow_out = _resolve_network_selector(network.get("allow_out"), rules) + deny_out = _resolve_network_selector(network.get("deny_out"), rules) + + body: Dict[str, Any] = {} + if allow_out is not None: + body["allow_out"] = allow_out + if deny_out is not None: + body["deny_out"] = deny_out + if "rules" in network and network["rules"] is not None: + body["rules"] = _build_client_rules(network["rules"]) + if "allow_public_traffic" in network: + body["allow_public_traffic"] = network["allow_public_traffic"] + if "mask_request_host" in network: + body["mask_request_host"] = network["mask_request_host"] + + return body + + def get_auto_resume_enabled(lifecycle: Optional[SandboxLifecycle]) -> Optional[bool]: if lifecycle is None or lifecycle.get("on_timeout") != "pause": return None @@ -125,17 +343,19 @@ def get_auto_resume_enabled(lifecycle: Optional[SandboxLifecycle]) -> Optional[b def from_client_network_config( - network: Union[Unset, ClientSandboxNetworkConfig], -) -> Optional[SandboxNetworkOpts]: + network: Union[Unset, SandboxNetworkConfig], +) -> Optional[SandboxNetworkInfo]: if isinstance(network, Unset): return None - result: SandboxNetworkOpts = {} + result: SandboxNetworkInfo = {} if not isinstance(network.allow_out, Unset): result["allow_out"] = list(network.allow_out) if not isinstance(network.deny_out, Unset): result["deny_out"] = list(network.deny_out) + if not isinstance(network.rules, Unset): + result["rules"] = cast(SandboxNetworkRules, network.rules.to_dict()) if not isinstance(network.allow_public_traffic, Unset): result["allow_public_traffic"] = network.allow_public_traffic if not isinstance(network.mask_request_host, Unset): @@ -188,7 +408,7 @@ class SandboxInfo: """Envd access token.""" allow_internet_access: Optional[bool] = None """Whether internet access was explicitly enabled or disabled for the sandbox.""" - network: Optional[SandboxNetworkOpts] = None + network: Optional[SandboxNetworkInfo] = None """Sandbox network configuration.""" lifecycle: Optional[SandboxInfoLifecycle] = None """Sandbox lifecycle configuration.""" @@ -202,7 +422,7 @@ def _from_sandbox_data( envd_access_token: Optional[str] = None, sandbox_domain: Optional[str] = None, allow_internet_access: Optional[bool] = None, - network: Optional[SandboxNetworkOpts] = None, + network: Optional[SandboxNetworkInfo] = None, lifecycle: Optional[SandboxInfoLifecycle] = None, ): return cls( diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index 3dd7044ea7..c145c8c76d 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -193,7 +193,7 @@ async def create( :param secure: Envd is secured with access token and cannot be used without it, defaults to `True`. :param allow_internet_access: Allow sandbox to access the internet, defaults to `True`. If set to `False`, it works the same as setting network `deny_out` to `[0.0.0.0/0]`. :param mcp: MCP server to enable in the sandbox - :param network: Sandbox network configuration + :param network: Sandbox network configuration. ``allow_out``/``deny_out`` may also be a callable receiving a :class:`SandboxNetworkSelectorContext` (``ctx.all_traffic``, ``ctx.rules``) and returning a list of strings. Per-host transform rules are nested under ``network.rules``. :param lifecycle: Sandbox lifecycle configuration — ``on_timeout``: ``"kill"`` (default) or ``"pause"``; ``auto_resume``: ``False`` (default) or ``True`` (only when ``on_timeout="pause"``). Example: ``{"on_timeout": "pause", "auto_resume": True}`` :param volume_mounts: Dictionary mapping mount paths to AsyncVolume instances or volume names diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py index 8946b038fa..1b9f6dc324 100644 --- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py @@ -38,6 +38,7 @@ from e2b.sandbox.main import SandboxBase from e2b.sandbox.sandbox_api import ( SandboxLifecycle, + build_network_config, get_auto_resume_enabled, McpServer, SandboxInfo, @@ -181,6 +182,7 @@ async def _create_sandbox( lifecycle["on_timeout"] == "pause" if lifecycle is not None else auto_pause ) auto_resume_enabled = get_auto_resume_enabled(lifecycle) + network_body = build_network_config(network) body = NewSandbox( template_id=template, auto_pause=(should_auto_pause if should_auto_pause is not None else UNSET), @@ -190,7 +192,7 @@ async def _create_sandbox( mcp=cast(Any, mcp) or UNSET, secure=secure, allow_internet_access=allow_internet_access, - network=SandboxNetworkConfig(**network) if network else UNSET, + network=SandboxNetworkConfig(**network_body) if network_body else UNSET, volume_mounts=volume_mounts if volume_mounts else UNSET, ) if auto_resume_enabled is not None: diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index 43f3a858c5..38f24bf3cd 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -191,7 +191,7 @@ def create( :param secure: Envd is secured with access token and cannot be used without it, defaults to `True`. :param allow_internet_access: Allow sandbox to access the internet, defaults to `True`. If set to `False`, it works the same as setting network `deny_out` to `[0.0.0.0/0]`. :param mcp: MCP server to enable in the sandbox - :param network: Sandbox network configuration + :param network: Sandbox network configuration. ``allow_out``/``deny_out`` may also be a callable receiving a :class:`SandboxNetworkSelectorContext` (``ctx.all_traffic``, ``ctx.rules``) and returning a list of strings. Per-host transform rules are nested under ``network.rules``. :param lifecycle: Sandbox lifecycle configuration — ``on_timeout``: ``"kill"`` (default) or ``"pause"``; ``auto_resume``: ``False`` (default) or ``True`` (only when ``on_timeout="pause"``). Example: ``{"on_timeout": "pause", "auto_resume": True}`` :param volume_mounts: Dictionary mapping mount paths to Volume instances or volume names diff --git a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py index 4cad1247dc..7a9d914a25 100644 --- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py @@ -37,6 +37,7 @@ from e2b.sandbox.main import SandboxBase from e2b.sandbox.sandbox_api import ( SandboxLifecycle, + build_network_config, get_auto_resume_enabled, McpServer, SandboxInfo, @@ -180,6 +181,7 @@ def _create_sandbox( lifecycle["on_timeout"] == "pause" if lifecycle is not None else auto_pause ) auto_resume_enabled = get_auto_resume_enabled(lifecycle) + network_body = build_network_config(network) body = NewSandbox( template_id=template, auto_pause=(should_auto_pause if should_auto_pause is not None else UNSET), @@ -189,7 +191,7 @@ def _create_sandbox( mcp=cast(Any, mcp) or UNSET, secure=secure, allow_internet_access=allow_internet_access, - network=SandboxNetworkConfig(**network) if network else UNSET, + network=SandboxNetworkConfig(**network_body) if network_body else UNSET, volume_mounts=volume_mounts if volume_mounts else UNSET, ) if auto_resume_enabled is not None: diff --git a/packages/python-sdk/tests/async/sandbox_async/test_network.py b/packages/python-sdk/tests/async/sandbox_async/test_network.py index 8c008371cf..c41e214889 100644 --- a/packages/python-sdk/tests/async/sandbox_async/test_network.py +++ b/packages/python-sdk/tests/async/sandbox_async/test_network.py @@ -1,6 +1,8 @@ +import json + import pytest -from e2b import ALL_TRAFFIC, SandboxNetworkOpts +from e2b import SandboxNetworkOpts from e2b.sandbox.commands.command_handle import CommandExitException @@ -8,7 +10,9 @@ async def test_allow_specific_ip_with_deny_all(async_sandbox_factory): """Test that sandbox with denyOut all and allowOut creates a whitelist.""" async_sandbox = await async_sandbox_factory( - network=SandboxNetworkOpts(deny_out=[ALL_TRAFFIC], allow_out=["1.1.1.1"]) + network=SandboxNetworkOpts( + deny_out=lambda ctx: [ctx.all_traffic], allow_out=["1.1.1.1"] + ) ) # Test that allowed IP works @@ -50,9 +54,9 @@ async def test_deny_specific_ip(async_sandbox_factory): @pytest.mark.skip_debug() async def test_deny_all_traffic(async_sandbox_factory): - """Test that sandbox can deny all traffic using all_traffic helper.""" + """Test that sandbox can deny all traffic using the all_traffic selector.""" async_sandbox = await async_sandbox_factory( - network=SandboxNetworkOpts(deny_out=[ALL_TRAFFIC]), timeout=30 + network=SandboxNetworkOpts(deny_out=lambda ctx: [ctx.all_traffic]), timeout=30 ) # Test that all traffic is denied @@ -74,7 +78,7 @@ async def test_allow_takes_precedence_over_deny(async_sandbox_factory): """Test that allowOut takes precedence over denyOut.""" async_sandbox = await async_sandbox_factory( network=SandboxNetworkOpts( - deny_out=[ALL_TRAFFIC], allow_out=["1.1.1.1", "8.8.8.8"] + deny_out=lambda ctx: [ctx.all_traffic], allow_out=["1.1.1.1", "8.8.8.8"] ) ) @@ -159,6 +163,35 @@ async def test_allow_public_traffic_true(async_sandbox_factory): assert response.status_code == 200 +@pytest.mark.skip_debug() +async def test_firewall_transform_injects_headers(async_sandbox_factory): + """Test that a firewall rule with a transform injects headers into outbound requests.""" + injected_header = "X-E2B-Test-Token" + injected_value = "e2b-transform-value-123" + + network: SandboxNetworkOpts = { + "allow_out": lambda ctx: list(ctx.rules.keys()), + "rules": { + "httpbin.org": [ + {"transform": {"headers": {injected_header: injected_value}}}, + ], + }, + } + async_sandbox = await async_sandbox_factory(network=network) + + result = await async_sandbox.commands.run( + "curl -sS --max-time 10 https://httpbin.org/headers" + ) + assert result.exit_code == 0 + + parsed = json.loads(result.stdout) + reflected = parsed["headers"].get(injected_header) + assert reflected == injected_value, ( + f"expected httpbin to reflect {injected_header}={injected_value}, " + f"got headers: {parsed['headers']}" + ) + + @pytest.mark.skip_debug() async def test_mask_request_host(async_sandbox_factory): """Test that mask_request_host modifies the Host header correctly.""" diff --git a/packages/python-sdk/tests/sync/sandbox_sync/test_network.py b/packages/python-sdk/tests/sync/sandbox_sync/test_network.py index 0cbe32d972..689553e159 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/test_network.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/test_network.py @@ -1,6 +1,8 @@ +import json + import pytest -from e2b import ALL_TRAFFIC, SandboxNetworkOpts +from e2b import SandboxNetworkOpts from e2b.sandbox.commands.command_handle import CommandExitException @@ -8,7 +10,9 @@ def test_allow_specific_ip_with_deny_all(sandbox_factory): """Test that sandbox with denyOut all and allowOut creates a whitelist.""" sandbox = sandbox_factory( - network=SandboxNetworkOpts(deny_out=[ALL_TRAFFIC], allow_out=["1.1.1.1"]) + network=SandboxNetworkOpts( + deny_out=lambda ctx: [ctx.all_traffic], allow_out=["1.1.1.1"] + ) ) # Test that allowed IP works @@ -48,9 +52,9 @@ def test_deny_specific_ip(sandbox_factory): @pytest.mark.skip_debug() def test_deny_all_traffic(sandbox_factory): - """Test that sandbox can deny all traffic using all_traffic helper.""" + """Test that sandbox can deny all traffic using the all_traffic selector.""" sandbox = sandbox_factory( - network=SandboxNetworkOpts(deny_out=[ALL_TRAFFIC]), timeout=30 + network=SandboxNetworkOpts(deny_out=lambda ctx: [ctx.all_traffic]), timeout=30 ) # Test that all traffic is denied @@ -72,7 +76,7 @@ def test_allow_takes_precedence_over_deny(sandbox_factory): """Test that allowOut takes precedence over denyOut.""" sandbox = sandbox_factory( network=SandboxNetworkOpts( - deny_out=[ALL_TRAFFIC], allow_out=["1.1.1.1", "8.8.8.8"] + deny_out=lambda ctx: [ctx.all_traffic], allow_out=["1.1.1.1", "8.8.8.8"] ) ) @@ -157,6 +161,33 @@ def test_allow_public_traffic_true(sandbox_factory): assert response.status_code == 200 +@pytest.mark.skip_debug() +def test_firewall_transform_injects_headers(sandbox_factory): + """Test that a firewall rule with a transform injects headers into outbound requests.""" + injected_header = "X-E2B-Test-Token" + injected_value = "e2b-transform-value-123" + + network: SandboxNetworkOpts = { + "allow_out": lambda ctx: list(ctx.rules.keys()), + "rules": { + "httpbin.org": [ + {"transform": {"headers": {injected_header: injected_value}}}, + ], + }, + } + sandbox = sandbox_factory(network=network) + + result = sandbox.commands.run("curl -sS --max-time 10 https://httpbin.org/headers") + assert result.exit_code == 0 + + parsed = json.loads(result.stdout) + reflected = parsed["headers"].get(injected_header) + assert reflected == injected_value, ( + f"expected httpbin to reflect {injected_header}={injected_value}, " + f"got headers: {parsed['headers']}" + ) + + @pytest.mark.skip_debug() def test_mask_request_host(sandbox_factory): """Test that mask_request_host modifies the Host header correctly.""" diff --git a/spec/openapi.yml b/spec/openapi.yml index 87084f64b9..898574b630 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -115,42 +115,42 @@ components: type: string responses: - '400': + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Error' - '401': + $ref: "#/components/schemas/Error" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/Error' - '403': + $ref: "#/components/schemas/Error" + "403": description: Forbidden content: application/json: schema: - $ref: '#/components/schemas/Error' - '404': + $ref: "#/components/schemas/Error" + "404": description: Not found content: application/json: schema: - $ref: '#/components/schemas/Error' - '409': + $ref: "#/components/schemas/Error" + "409": description: Conflict content: application/json: schema: - $ref: '#/components/schemas/Error' - '500': + $ref: "#/components/schemas/Error" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: "#/components/schemas/Error" schemas: Team: @@ -271,17 +271,47 @@ components: description: Specify if the sandbox URLs should be accessible only with authentication. allowOut: type: array - description: List of allowed CIDR blocks or IP addresses for egress traffic. Allowed addresses always take precedence over blocked addresses. + description: List of allowed destinations for egress traffic. Each entry can be a CIDR block (e.g. "8.8.8.8/32"), a bare IP address (e.g. "8.8.8.8"), or a domain name (e.g. "example.com", "*.example.com"). Allowed entries always take precedence over denied entries. items: type: string denyOut: type: array - description: List of denied CIDR blocks or IP addresses for egress traffic + description: List of denied CIDR blocks or IP addresses for egress traffic. Domain names are not supported for deny rules. items: type: string maskRequestHost: type: string description: Specify host mask which will be used for all sandbox requests + rules: + type: object + description: > + Per-domain transform rules applied to matching egress HTTP/HTTPS requests. + Keys are domains (e.g. "api.example.com", "example.com"). + A domain listed here is not automatically allowed - use allowOut to permit the traffic. + additionalProperties: + type: array + items: + $ref: '#/components/schemas/SandboxNetworkRule' + + SandboxNetworkRule: + type: object + description: Transform rule applied to egress requests matching a domain pattern. + properties: + transform: + $ref: '#/components/schemas/SandboxNetworkTransform' + + SandboxNetworkTransform: + type: object + description: Transformations applied to matching egress requests before forwarding. + properties: + headers: + type: object + description: > + HTTP headers to inject or override in matching requests. + An existing header with the same name is replaced. Values are plain strings; + secret resolution happens client-side before sending to the API. + additionalProperties: + type: string SandboxAutoResumeEnabled: type: boolean @@ -346,7 +376,7 @@ components: type: string description: Log message content level: - $ref: '#/components/schemas/LogLevel' + $ref: "#/components/schemas/LogLevel" fields: type: object additionalProperties: @@ -361,12 +391,12 @@ components: description: Logs of the sandbox type: array items: - $ref: '#/components/schemas/SandboxLog' + $ref: "#/components/schemas/SandboxLog" logEntries: description: Structured logs of the sandbox type: array items: - $ref: '#/components/schemas/SandboxLogEntry' + $ref: "#/components/schemas/SandboxLogEntry" SandboxLogsV2Response: required: @@ -377,7 +407,7 @@ components: description: Sandbox logs structured type: array items: - $ref: '#/components/schemas/SandboxLogEntry' + $ref: "#/components/schemas/SandboxLogEntry" SandboxMetric: description: Metric entry with timestamp and line @@ -388,6 +418,7 @@ components: - cpuUsedPct - memUsed - memTotal + - memCache - diskUsed - diskTotal properties: @@ -416,6 +447,10 @@ components: type: integer format: int64 description: Total memory in bytes + memCache: + type: integer + format: int64 + description: Cached memory (page cache) in bytes diskUsed: type: integer format: int64 @@ -459,7 +494,7 @@ components: deprecated: true description: Identifier of the client envdVersion: - $ref: '#/components/schemas/EnvdVersion' + $ref: "#/components/schemas/EnvdVersion" envdAccessToken: type: string description: Access token used for envd communication @@ -507,7 +542,7 @@ components: format: date-time description: Time when the sandbox will expire envdVersion: - $ref: '#/components/schemas/EnvdVersion' + $ref: "#/components/schemas/EnvdVersion" envdAccessToken: type: string description: Access token used for envd communication @@ -520,23 +555,23 @@ components: nullable: true description: Base domain where the sandbox traffic is accessible cpuCount: - $ref: '#/components/schemas/CPUCount' + $ref: "#/components/schemas/CPUCount" memoryMB: - $ref: '#/components/schemas/MemoryMB' + $ref: "#/components/schemas/MemoryMB" diskSizeMB: - $ref: '#/components/schemas/DiskSizeMB' + $ref: "#/components/schemas/DiskSizeMB" metadata: - $ref: '#/components/schemas/SandboxMetadata' + $ref: "#/components/schemas/SandboxMetadata" state: - $ref: '#/components/schemas/SandboxState' + $ref: "#/components/schemas/SandboxState" network: - $ref: '#/components/schemas/SandboxNetworkConfig' + $ref: "#/components/schemas/SandboxNetworkConfig" lifecycle: - $ref: '#/components/schemas/SandboxLifecycle' + $ref: "#/components/schemas/SandboxLifecycle" volumeMounts: type: array items: - $ref: '#/components/schemas/SandboxVolumeMount' + $ref: "#/components/schemas/SandboxVolumeMount" ListedSandbox: required: @@ -573,21 +608,21 @@ components: format: date-time description: Time when the sandbox will expire cpuCount: - $ref: '#/components/schemas/CPUCount' + $ref: "#/components/schemas/CPUCount" memoryMB: - $ref: '#/components/schemas/MemoryMB' + $ref: "#/components/schemas/MemoryMB" diskSizeMB: - $ref: '#/components/schemas/DiskSizeMB' + $ref: "#/components/schemas/DiskSizeMB" metadata: - $ref: '#/components/schemas/SandboxMetadata' + $ref: "#/components/schemas/SandboxMetadata" state: - $ref: '#/components/schemas/SandboxState' + $ref: "#/components/schemas/SandboxState" envdVersion: - $ref: '#/components/schemas/EnvdVersion' + $ref: "#/components/schemas/EnvdVersion" volumeMounts: type: array items: - $ref: '#/components/schemas/SandboxVolumeMount' + $ref: "#/components/schemas/SandboxVolumeMount" SandboxesWithMetrics: required: @@ -595,7 +630,7 @@ components: properties: sandboxes: additionalProperties: - $ref: '#/components/schemas/SandboxMetric' + $ref: "#/components/schemas/SandboxMetric" NewSandbox: required: @@ -615,7 +650,7 @@ components: default: false description: Automatically pauses the sandbox after the timeout autoResume: - $ref: '#/components/schemas/SandboxAutoResumeConfig' + $ref: "#/components/schemas/SandboxAutoResumeConfig" secure: type: boolean description: Secure all system communication with sandbox @@ -625,17 +660,17 @@ components: Allow sandbox to access the internet. When set to false, it behaves the same as specifying denyOut to 0.0.0.0/0 in the network config. network: - $ref: '#/components/schemas/SandboxNetworkConfig' + $ref: "#/components/schemas/SandboxNetworkConfig" metadata: - $ref: '#/components/schemas/SandboxMetadata' + $ref: "#/components/schemas/SandboxMetadata" envVars: - $ref: '#/components/schemas/EnvVars' + $ref: "#/components/schemas/EnvVars" mcp: - $ref: '#/components/schemas/Mcp' + $ref: "#/components/schemas/Mcp" volumeMounts: type: array items: - $ref: '#/components/schemas/SandboxVolumeMount' + $ref: "#/components/schemas/SandboxVolumeMount" ResumedSandbox: properties: @@ -765,11 +800,11 @@ components: type: string description: Identifier of the last successful build for given template cpuCount: - $ref: '#/components/schemas/CPUCount' + $ref: "#/components/schemas/CPUCount" memoryMB: - $ref: '#/components/schemas/MemoryMB' + $ref: "#/components/schemas/MemoryMB" diskSizeMB: - $ref: '#/components/schemas/DiskSizeMB' + $ref: "#/components/schemas/DiskSizeMB" public: type: boolean description: Whether the template is public or only accessible by the team @@ -794,7 +829,7 @@ components: description: Time when the template was last updated createdBy: allOf: - - $ref: '#/components/schemas/TeamUser' + - $ref: "#/components/schemas/TeamUser" nullable: true lastSpawnedAt: type: string @@ -810,9 +845,9 @@ components: format: int32 description: Number of times the template was built envdVersion: - $ref: '#/components/schemas/EnvdVersion' + $ref: "#/components/schemas/EnvdVersion" buildStatus: - $ref: '#/components/schemas/TemplateBuildStatus' + $ref: "#/components/schemas/TemplateBuildStatus" TemplateRequestResponseV3: required: @@ -873,11 +908,11 @@ components: type: string description: Identifier of the last successful build for given template cpuCount: - $ref: '#/components/schemas/CPUCount' + $ref: "#/components/schemas/CPUCount" memoryMB: - $ref: '#/components/schemas/MemoryMB' + $ref: "#/components/schemas/MemoryMB" diskSizeMB: - $ref: '#/components/schemas/DiskSizeMB' + $ref: "#/components/schemas/DiskSizeMB" public: type: boolean description: Whether the template is public or only accessible by the team @@ -896,7 +931,7 @@ components: description: Time when the template was last updated createdBy: allOf: - - $ref: '#/components/schemas/TeamUser' + - $ref: "#/components/schemas/TeamUser" nullable: true lastSpawnedAt: type: string @@ -912,7 +947,7 @@ components: format: int32 description: Number of times the template was built envdVersion: - $ref: '#/components/schemas/EnvdVersion' + $ref: "#/components/schemas/EnvdVersion" TemplateBuild: required: @@ -928,7 +963,7 @@ components: format: uuid description: Identifier of the build status: - $ref: '#/components/schemas/TemplateBuildStatus' + $ref: "#/components/schemas/TemplateBuildStatus" createdAt: type: string format: date-time @@ -942,13 +977,13 @@ components: format: date-time description: Time when the build was finished cpuCount: - $ref: '#/components/schemas/CPUCount' + $ref: "#/components/schemas/CPUCount" memoryMB: - $ref: '#/components/schemas/MemoryMB' + $ref: "#/components/schemas/MemoryMB" diskSizeMB: - $ref: '#/components/schemas/DiskSizeMB' + $ref: "#/components/schemas/DiskSizeMB" envdVersion: - $ref: '#/components/schemas/EnvdVersion' + $ref: "#/components/schemas/EnvdVersion" TemplateWithBuilds: required: @@ -1000,7 +1035,7 @@ components: type: array description: List of builds for the template items: - $ref: '#/components/schemas/TemplateBuild' + $ref: "#/components/schemas/TemplateBuild" TemplateAliasResponse: required: @@ -1034,9 +1069,9 @@ components: description: Ready check command to execute in the template after the build type: string cpuCount: - $ref: '#/components/schemas/CPUCount' + $ref: "#/components/schemas/CPUCount" memoryMB: - $ref: '#/components/schemas/MemoryMB' + $ref: "#/components/schemas/MemoryMB" TemplateStep: description: Step in the template build process @@ -1079,9 +1114,9 @@ components: type: string description: Identifier of the team cpuCount: - $ref: '#/components/schemas/CPUCount' + $ref: "#/components/schemas/CPUCount" memoryMB: - $ref: '#/components/schemas/MemoryMB' + $ref: "#/components/schemas/MemoryMB" TemplateBuildRequestV2: required: @@ -1095,21 +1130,21 @@ components: type: string description: Identifier of the team cpuCount: - $ref: '#/components/schemas/CPUCount' + $ref: "#/components/schemas/CPUCount" memoryMB: - $ref: '#/components/schemas/MemoryMB' + $ref: "#/components/schemas/MemoryMB" FromImageRegistry: oneOf: - - $ref: '#/components/schemas/AWSRegistry' - - $ref: '#/components/schemas/GCPRegistry' - - $ref: '#/components/schemas/GeneralRegistry' + - $ref: "#/components/schemas/AWSRegistry" + - $ref: "#/components/schemas/GCPRegistry" + - $ref: "#/components/schemas/GeneralRegistry" discriminator: propertyName: type mapping: - aws: '#/components/schemas/AWSRegistry' - gcp: '#/components/schemas/GCPRegistry' - registry: '#/components/schemas/GeneralRegistry' + aws: "#/components/schemas/AWSRegistry" + gcp: "#/components/schemas/GCPRegistry" + registry: "#/components/schemas/GeneralRegistry" AWSRegistry: type: object @@ -1175,7 +1210,7 @@ components: type: string description: Template to use as a base for the template build fromImageRegistry: - $ref: '#/components/schemas/FromImageRegistry' + $ref: "#/components/schemas/FromImageRegistry" force: default: false type: boolean @@ -1185,7 +1220,7 @@ components: description: List of steps to execute in the template build type: array items: - $ref: '#/components/schemas/TemplateStep' + $ref: "#/components/schemas/TemplateStep" startCmd: description: Start command to execute in the template after the build type: string @@ -1227,7 +1262,7 @@ components: type: string description: Log message content level: - $ref: '#/components/schemas/LogLevel' + $ref: "#/components/schemas/LogLevel" step: type: string description: Step in the build process related to the log entry @@ -1247,7 +1282,7 @@ components: description: Log entries related to the status reason type: array items: - $ref: '#/components/schemas/BuildLogEntry' + $ref: "#/components/schemas/BuildLogEntry" TemplateBuildStatus: type: string @@ -1277,7 +1312,7 @@ components: description: Build logs structured type: array items: - $ref: '#/components/schemas/BuildLogEntry' + $ref: "#/components/schemas/BuildLogEntry" templateID: type: string description: Identifier of the template @@ -1285,9 +1320,9 @@ components: type: string description: Identifier of the build status: - $ref: '#/components/schemas/TemplateBuildStatus' + $ref: "#/components/schemas/TemplateBuildStatus" reason: - $ref: '#/components/schemas/BuildStatusReason' + $ref: "#/components/schemas/BuildStatusReason" TemplateBuildLogsResponse: required: @@ -1298,7 +1333,7 @@ components: description: Build logs structured type: array items: - $ref: '#/components/schemas/BuildLogEntry' + $ref: "#/components/schemas/BuildLogEntry" LogsDirection: type: string @@ -1322,17 +1357,22 @@ components: NodeStatus: type: string - description: Status of the node + description: | + Status of the node. + - draining: the node is bound to be shut down. It will not accept new sandboxes and will stop once all existing sandboxes are done. + - standby: the node is not actively used, but it can return to ready and continue serving traffic. enum: - ready - draining - connecting - unhealthy + - standby x-enum-varnames: - NodeStatusReady - NodeStatusDraining - NodeStatusConnecting - NodeStatusUnhealthy + - NodeStatusStandby NodeStatusChange: required: @@ -1343,7 +1383,7 @@ components: format: uuid description: Identifier of the cluster status: - $ref: '#/components/schemas/NodeStatus' + $ref: "#/components/schemas/NodeStatus" DiskMetrics: required: @@ -1410,7 +1450,7 @@ components: type: array description: Detailed metrics for each disk/mount point items: - $ref: '#/components/schemas/DiskMetrics' + $ref: "#/components/schemas/DiskMetrics" MachineInfo: required: - cpuFamily @@ -1462,15 +1502,15 @@ components: type: string description: Identifier of the cluster machineInfo: - $ref: '#/components/schemas/MachineInfo' + $ref: "#/components/schemas/MachineInfo" status: - $ref: '#/components/schemas/NodeStatus' + $ref: "#/components/schemas/NodeStatus" sandboxCount: type: integer format: uint32 description: Number of sandboxes running on the node metrics: - $ref: '#/components/schemas/NodeMetrics' + $ref: "#/components/schemas/NodeMetrics" createSuccesses: type: integer format: uint64 @@ -1515,15 +1555,15 @@ components: type: string description: Service instance identifier of the node machineInfo: - $ref: '#/components/schemas/MachineInfo' + $ref: "#/components/schemas/MachineInfo" status: - $ref: '#/components/schemas/NodeStatus' + $ref: "#/components/schemas/NodeStatus" sandboxCount: type: integer format: uint32 description: Number of sandboxes running on the node metrics: - $ref: '#/components/schemas/NodeMetrics' + $ref: "#/components/schemas/NodeMetrics" cachedBuilds: type: array description: List of cached builds id on the node @@ -1557,7 +1597,7 @@ components: type: string description: The fully created access token mask: - $ref: '#/components/schemas/IdentifierMaskingDetails' + $ref: "#/components/schemas/IdentifierMaskingDetails" createdAt: type: string format: date-time @@ -1586,14 +1626,14 @@ components: type: string description: Name of the API key mask: - $ref: '#/components/schemas/IdentifierMaskingDetails' + $ref: "#/components/schemas/IdentifierMaskingDetails" createdAt: type: string format: date-time description: Timestamp of API key creation createdBy: allOf: - - $ref: '#/components/schemas/TeamUser' + - $ref: "#/components/schemas/TeamUser" nullable: true lastUsed: type: string @@ -1617,7 +1657,7 @@ components: type: string description: Raw value of the API key mask: - $ref: '#/components/schemas/IdentifierMaskingDetails' + $ref: "#/components/schemas/IdentifierMaskingDetails" name: type: string description: Name of the API key @@ -1627,7 +1667,7 @@ components: description: Timestamp of API key creation createdBy: allOf: - - $ref: '#/components/schemas/TeamUser' + - $ref: "#/components/schemas/TeamUser" nullable: true lastUsed: type: string @@ -1781,7 +1821,7 @@ components: name: type: string description: Name of the volume - pattern: '^[a-zA-Z0-9_-]+$' + pattern: "^[a-zA-Z0-9_-]+$" required: - name @@ -1799,10 +1839,10 @@ paths: get: description: Health check responses: - '200': - description: Request was successful - '401': - $ref: '#/components/responses/401' + "204": + description: The service is healthy + "401": + $ref: "#/components/responses/401" /teams: get: @@ -1812,7 +1852,7 @@ paths: - AccessTokenAuth: [] - Supabase1TokenAuth: [] responses: - '200': + "200": description: Successfully returned all teams content: application/json: @@ -1820,11 +1860,11 @@ paths: type: array items: allOf: - - $ref: '#/components/schemas/Team' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + - $ref: "#/components/schemas/Team" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /teams/{teamID}/metrics: get: @@ -1835,7 +1875,7 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/teamID' + - $ref: "#/components/parameters/teamID" - in: query name: start schema: @@ -1851,22 +1891,22 @@ paths: minimum: 0 description: Unix timestamp for the end of the interval, in seconds, for which the metrics responses: - '200': + "200": description: Successfully returned the team metrics content: application/json: schema: type: array items: - $ref: '#/components/schemas/TeamMetric' - '400': - $ref: '#/components/responses/400' - '401': - $ref: '#/components/responses/401' - '403': - $ref: '#/components/responses/403' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/TeamMetric" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "403": + $ref: "#/components/responses/403" + "500": + $ref: "#/components/responses/500" /teams/{teamID}/metrics/max: get: @@ -1877,7 +1917,7 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/teamID' + - $ref: "#/components/parameters/teamID" - in: query name: start schema: @@ -1900,20 +1940,20 @@ paths: enum: [concurrent_sandboxes, sandbox_start_rate] description: Metric to retrieve the maximum value for responses: - '200': + "200": description: Successfully returned the team metrics content: application/json: schema: - $ref: '#/components/schemas/MaxTeamMetric' - '400': - $ref: '#/components/responses/400' - '401': - $ref: '#/components/responses/401' - '403': - $ref: '#/components/responses/403' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/MaxTeamMetric" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "403": + $ref: "#/components/responses/403" + "500": + $ref: "#/components/responses/500" /sandboxes: get: @@ -1931,7 +1971,7 @@ paths: schema: type: string responses: - '200': + "200": description: Successfully returned all running sandboxes content: application/json: @@ -1939,13 +1979,13 @@ paths: type: array items: allOf: - - $ref: '#/components/schemas/ListedSandbox' - '401': - $ref: '#/components/responses/401' - '400': - $ref: '#/components/responses/400' - '500': - $ref: '#/components/responses/500' + - $ref: "#/components/schemas/ListedSandbox" + "401": + $ref: "#/components/responses/401" + "400": + $ref: "#/components/responses/400" + "500": + $ref: "#/components/responses/500" post: description: Create a sandbox from the template tags: [sandboxes] @@ -1958,20 +1998,20 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/NewSandbox' + $ref: "#/components/schemas/NewSandbox" responses: - '201': + "201": description: The sandbox was created successfully content: application/json: schema: - $ref: '#/components/schemas/Sandbox' - '401': - $ref: '#/components/responses/401' - '400': - $ref: '#/components/responses/400' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/Sandbox" + "401": + $ref: "#/components/responses/401" + "400": + $ref: "#/components/responses/400" + "500": + $ref: "#/components/responses/500" /v2/sandboxes: get: @@ -1995,13 +2035,13 @@ paths: schema: type: array items: - $ref: '#/components/schemas/SandboxState' + $ref: "#/components/schemas/SandboxState" style: form explode: false - - $ref: '#/components/parameters/paginationNextToken' - - $ref: '#/components/parameters/paginationLimit' + - $ref: "#/components/parameters/paginationNextToken" + - $ref: "#/components/parameters/paginationLimit" responses: - '200': + "200": description: Successfully returned all running sandboxes content: application/json: @@ -2009,13 +2049,13 @@ paths: type: array items: allOf: - - $ref: '#/components/schemas/ListedSandbox' - '401': - $ref: '#/components/responses/401' - '400': - $ref: '#/components/responses/400' - '500': - $ref: '#/components/responses/500' + - $ref: "#/components/schemas/ListedSandbox" + "401": + $ref: "#/components/responses/401" + "400": + $ref: "#/components/responses/400" + "500": + $ref: "#/components/responses/500" /sandboxes/metrics: get: @@ -2038,18 +2078,18 @@ paths: maxItems: 100 uniqueItems: true responses: - '200': + "200": description: Successfully returned all running sandboxes with metrics content: application/json: schema: - $ref: '#/components/schemas/SandboxesWithMetrics' - '401': - $ref: '#/components/responses/401' - '400': - $ref: '#/components/responses/400' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/SandboxesWithMetrics" + "401": + $ref: "#/components/responses/401" + "400": + $ref: "#/components/responses/400" + "500": + $ref: "#/components/responses/500" /sandboxes/{sandboxID}/logs: get: @@ -2061,7 +2101,7 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/sandboxID' + - $ref: "#/components/parameters/sandboxID" - in: query name: start schema: @@ -2078,18 +2118,18 @@ paths: type: integer description: Maximum number of logs that should be returned responses: - '200': + "200": description: Successfully returned the sandbox logs content: application/json: schema: - $ref: '#/components/schemas/SandboxLogs' - '404': - $ref: '#/components/responses/404' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/SandboxLogs" + "404": + $ref: "#/components/responses/404" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /v2/sandboxes/{sandboxID}/logs: get: @@ -2100,7 +2140,7 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/sandboxID' + - $ref: "#/components/parameters/sandboxID" - in: query name: cursor schema: @@ -2120,12 +2160,12 @@ paths: - in: query name: direction schema: - $ref: '#/components/schemas/LogsDirection' + $ref: "#/components/schemas/LogsDirection" description: Direction of the logs that should be returned - in: query name: level schema: - $ref: '#/components/schemas/LogLevel' + $ref: "#/components/schemas/LogLevel" description: Minimum log level to return. Logs below this level are excluded - in: query name: search @@ -2134,18 +2174,18 @@ paths: maxLength: 256 description: Case-sensitive substring match on log message content responses: - '200': + "200": description: Successfully returned the sandbox logs content: application/json: schema: - $ref: '#/components/schemas/SandboxLogsV2Response' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/SandboxLogsV2Response" + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" /sandboxes/{sandboxID}: get: @@ -2156,20 +2196,20 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/sandboxID' + - $ref: "#/components/parameters/sandboxID" responses: - '200': + "200": description: Successfully returned the sandbox content: application/json: schema: - $ref: '#/components/schemas/SandboxDetail' - '404': - $ref: '#/components/responses/404' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/SandboxDetail" + "404": + $ref: "#/components/responses/404" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" delete: description: Kill a sandbox @@ -2179,16 +2219,16 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/sandboxID' + - $ref: "#/components/parameters/sandboxID" responses: - '204': + "204": description: The sandbox was killed successfully - '404': - $ref: '#/components/responses/404' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + "404": + $ref: "#/components/responses/404" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /sandboxes/{sandboxID}/metrics: get: @@ -2199,7 +2239,7 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/sandboxID' + - $ref: "#/components/parameters/sandboxID" - in: query name: start schema: @@ -2216,22 +2256,22 @@ paths: description: Unix timestamp for the end of the interval, in seconds, for which the metrics responses: - '200': + "200": description: Successfully returned the sandbox metrics content: application/json: schema: type: array items: - $ref: '#/components/schemas/SandboxMetric' - '400': - $ref: '#/components/responses/400' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/SandboxMetric" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" # TODO: Pause and resume might be exposed as POST /sandboxes/{sandboxID}/snapshot and then POST /sandboxes with specified snapshotting setup /sandboxes/{sandboxID}/pause: @@ -2243,18 +2283,18 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/sandboxID' + - $ref: "#/components/parameters/sandboxID" responses: - '204': + "204": description: The sandbox was paused successfully and can be resumed - '409': - $ref: '#/components/responses/409' - '404': - $ref: '#/components/responses/404' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + "409": + $ref: "#/components/responses/409" + "404": + $ref: "#/components/responses/404" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /sandboxes/{sandboxID}/resume: post: @@ -2266,28 +2306,28 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/sandboxID' + - $ref: "#/components/parameters/sandboxID" requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/ResumedSandbox' + $ref: "#/components/schemas/ResumedSandbox" responses: - '201': + "201": description: The sandbox was resumed successfully content: application/json: schema: - $ref: '#/components/schemas/Sandbox' - '409': - $ref: '#/components/responses/409' - '404': - $ref: '#/components/responses/404' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/Sandbox" + "409": + $ref: "#/components/responses/409" + "404": + $ref: "#/components/responses/404" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /sandboxes/{sandboxID}/connect: post: @@ -2298,34 +2338,34 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/sandboxID' + - $ref: "#/components/parameters/sandboxID" requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/ConnectSandbox' + $ref: "#/components/schemas/ConnectSandbox" responses: - '200': + "200": description: The sandbox was already running content: application/json: schema: - $ref: '#/components/schemas/Sandbox' - '201': + $ref: "#/components/schemas/Sandbox" + "201": description: The sandbox was resumed successfully content: application/json: schema: - $ref: '#/components/schemas/Sandbox' - '400': - $ref: '#/components/responses/400' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/Sandbox" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" /sandboxes/{sandboxID}/timeout: post: @@ -2349,16 +2389,67 @@ paths: format: int32 minimum: 0 parameters: - - $ref: '#/components/parameters/sandboxID' + - $ref: "#/components/parameters/sandboxID" responses: - '204': + "204": description: Successfully set the sandbox timeout - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" + + /sandboxes/{sandboxID}/network: + put: + description: Update the network configuration for a running sandbox. Replaces the current egress rules with the provided configuration. Omitting both fields clears all egress rules. + security: + - ApiKeyAuth: [] + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] + tags: [sandboxes] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + allowOut: + type: array + description: List of allowed destinations for egress traffic. Each entry can be a CIDR block (e.g. "8.8.8.8/32"), a bare IP address (e.g. "8.8.8.8"), or a domain name (e.g. "example.com", "*.example.com"). Allowed entries always take precedence over denied entries. + items: + type: string + denyOut: + type: array + description: List of denied CIDR blocks or IP addresses for egress traffic. Domain names are not supported for deny rules. + items: + type: string + rules: + type: object + description: Per-domain transform rules. Replaces all existing rules when provided. + additionalProperties: + type: array + items: + $ref: '#/components/schemas/SandboxNetworkRule' + allow_internet_access: + type: boolean + description: + Allow sandbox to access the internet. When set to false, it behaves the same as specifying denyOut + to 0.0.0.0/0 in the network config. + parameters: + - $ref: "#/components/parameters/sandboxID" + responses: + "204": + description: Successfully updated the sandbox network configuration + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "409": + $ref: "#/components/responses/409" + "500": + $ref: "#/components/responses/500" /sandboxes/{sandboxID}/refreshes: post: @@ -2380,14 +2471,14 @@ paths: maximum: 3600 # 1 hour minimum: 0 parameters: - - $ref: '#/components/parameters/sandboxID' + - $ref: "#/components/parameters/sandboxID" responses: - '204': + "204": description: Successfully refreshed the sandbox - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" /sandboxes/{sandboxID}/snapshots: post: @@ -2398,7 +2489,7 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/sandboxID' + - $ref: "#/components/parameters/sandboxID" requestBody: required: true content: @@ -2410,20 +2501,20 @@ paths: type: string description: Optional name for the snapshot template. If a snapshot template with this name already exists, a new build will be assigned to the existing template instead of creating a new one. responses: - '201': + "201": description: Snapshot created successfully content: application/json: schema: - $ref: '#/components/schemas/SnapshotInfo' - '400': - $ref: '#/components/responses/400' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/SnapshotInfo" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" /snapshots: get: @@ -2440,21 +2531,21 @@ paths: schema: type: string description: Filter snapshots by source sandbox ID - - $ref: '#/components/parameters/paginationLimit' - - $ref: '#/components/parameters/paginationNextToken' + - $ref: "#/components/parameters/paginationLimit" + - $ref: "#/components/parameters/paginationNextToken" responses: - '200': + "200": description: Successfully returned snapshots content: application/json: schema: type: array items: - $ref: '#/components/schemas/SnapshotInfo' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/SnapshotInfo" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /v3/templates: post: @@ -2469,21 +2560,21 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TemplateBuildRequestV3' + $ref: "#/components/schemas/TemplateBuildRequestV3" responses: - '202': + "202": description: The build was requested successfully content: application/json: schema: - $ref: '#/components/schemas/TemplateRequestResponseV3' - '400': - $ref: '#/components/responses/400' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/TemplateRequestResponseV3" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /v2/templates: post: @@ -2499,21 +2590,21 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TemplateBuildRequestV2' + $ref: "#/components/schemas/TemplateBuildRequestV2" responses: - '202': + "202": description: The build was requested successfully content: application/json: schema: - $ref: '#/components/schemas/TemplateLegacy' - '400': - $ref: '#/components/responses/400' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/TemplateLegacy" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /templates/{templateID}/files/{hash}: get: @@ -2525,7 +2616,7 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/templateID' + - $ref: "#/components/parameters/templateID" - in: path name: hash required: true @@ -2534,20 +2625,20 @@ paths: description: Hash of the files responses: - '201': + "201": description: The upload link where to upload the tar file content: application/json: schema: - $ref: '#/components/schemas/TemplateBuildFileUpload' - '400': - $ref: '#/components/responses/400' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/TemplateBuildFileUpload" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" /templates: get: @@ -2566,7 +2657,7 @@ paths: type: string description: Identifier of the team responses: - '200': + "200": description: Successfully returned all templates content: application/json: @@ -2574,11 +2665,11 @@ paths: type: array items: allOf: - - $ref: '#/components/schemas/Template' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + - $ref: "#/components/schemas/Template" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" post: description: Create a new template deprecated: true @@ -2592,21 +2683,21 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TemplateBuildRequest' + $ref: "#/components/schemas/TemplateBuildRequest" responses: - '202': + "202": description: The build was accepted content: application/json: schema: - $ref: '#/components/schemas/TemplateLegacy' - '400': - $ref: '#/components/responses/400' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/TemplateLegacy" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /templates/{templateID}: get: @@ -2617,20 +2708,20 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/templateID' - - $ref: '#/components/parameters/paginationNextToken' - - $ref: '#/components/parameters/paginationLimit' + - $ref: "#/components/parameters/templateID" + - $ref: "#/components/parameters/paginationNextToken" + - $ref: "#/components/parameters/paginationLimit" responses: - '200': + "200": description: Successfully returned the template with its builds content: application/json: schema: - $ref: '#/components/schemas/TemplateWithBuilds' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/TemplateWithBuilds" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" post: description: Rebuild an template deprecated: true @@ -2640,25 +2731,25 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/templateID' + - $ref: "#/components/parameters/templateID" requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/TemplateBuildRequest' + $ref: "#/components/schemas/TemplateBuildRequest" responses: - '202': + "202": description: The build was accepted content: application/json: schema: - $ref: '#/components/schemas/TemplateLegacy' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/TemplateLegacy" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" delete: description: Delete a template tags: [templates] @@ -2668,14 +2759,14 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/templateID' + - $ref: "#/components/parameters/templateID" responses: - '204': + "204": description: The template was deleted successfully - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" patch: description: Update template deprecated: true @@ -2686,22 +2777,22 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/templateID' + - $ref: "#/components/parameters/templateID" requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/TemplateUpdateRequest' + $ref: "#/components/schemas/TemplateUpdateRequest" responses: - '200': + "200": description: The template was updated successfully - '400': - $ref: '#/components/responses/400' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /templates/{templateID}/builds/{buildID}: post: @@ -2713,15 +2804,15 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/templateID' - - $ref: '#/components/parameters/buildID' + - $ref: "#/components/parameters/templateID" + - $ref: "#/components/parameters/buildID" responses: - '202': + "202": description: The build has started - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /v2/templates/{templateID}/builds/{buildID}: post: @@ -2732,21 +2823,21 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/templateID' - - $ref: '#/components/parameters/buildID' + - $ref: "#/components/parameters/templateID" + - $ref: "#/components/parameters/buildID" requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/TemplateBuildStartV2' + $ref: "#/components/schemas/TemplateBuildStartV2" responses: - '202': + "202": description: The build has started - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /v2/templates/{templateID}: patch: @@ -2758,26 +2849,26 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/templateID' + - $ref: "#/components/parameters/templateID" requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/TemplateUpdateRequest' + $ref: "#/components/schemas/TemplateUpdateRequest" responses: - '200': + "200": description: The template was updated successfully content: application/json: schema: - $ref: '#/components/schemas/TemplateUpdateResponse' - '400': - $ref: '#/components/responses/400' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/TemplateUpdateResponse" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /templates/{templateID}/builds/{buildID}/status: get: @@ -2789,8 +2880,8 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/templateID' - - $ref: '#/components/parameters/buildID' + - $ref: "#/components/parameters/templateID" + - $ref: "#/components/parameters/buildID" - in: query name: logsOffset schema: @@ -2811,20 +2902,20 @@ paths: - in: query name: level schema: - $ref: '#/components/schemas/LogLevel' + $ref: "#/components/schemas/LogLevel" responses: - '200': + "200": description: Successfully returned the template content: application/json: schema: - $ref: '#/components/schemas/TemplateBuildInfo' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/TemplateBuildInfo" + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" /templates/{templateID}/builds/{buildID}/logs: get: @@ -2836,8 +2927,8 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/templateID' - - $ref: '#/components/parameters/buildID' + - $ref: "#/components/parameters/templateID" + - $ref: "#/components/parameters/buildID" - in: query name: cursor schema: @@ -2857,29 +2948,29 @@ paths: - in: query name: direction schema: - $ref: '#/components/schemas/LogsDirection' + $ref: "#/components/schemas/LogsDirection" - in: query name: level schema: - $ref: '#/components/schemas/LogLevel' + $ref: "#/components/schemas/LogLevel" - in: query name: source schema: - $ref: '#/components/schemas/LogsSource' + $ref: "#/components/schemas/LogsSource" description: Source of the logs that should be returned from responses: - '200': + "200": description: Successfully returned the template build logs content: application/json: schema: - $ref: '#/components/schemas/TemplateBuildLogsResponse' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/TemplateBuildLogsResponse" + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" /templates/tags: post: @@ -2894,22 +2985,22 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AssignTemplateTagsRequest' + $ref: "#/components/schemas/AssignTemplateTagsRequest" responses: - '201': + "201": description: Tag assigned successfully content: application/json: schema: - $ref: '#/components/schemas/AssignedTemplateTags' - '400': - $ref: '#/components/responses/400' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/AssignedTemplateTags" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" delete: description: Delete multiple tags from templates tags: [tags] @@ -2922,18 +3013,18 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/DeleteTemplateTagsRequest' + $ref: "#/components/schemas/DeleteTemplateTagsRequest" responses: - '204': + "204": description: Tags deleted successfully - '400': - $ref: '#/components/responses/400' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" /templates/{templateID}/tags: get: @@ -2944,24 +3035,24 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/templateID' + - $ref: "#/components/parameters/templateID" responses: - '200': + "200": description: Successfully returned the template tags content: application/json: schema: type: array items: - $ref: '#/components/schemas/TemplateTag' - '401': - $ref: '#/components/responses/401' - '403': - $ref: '#/components/responses/403' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/TemplateTag" + "401": + $ref: "#/components/responses/401" + "403": + $ref: "#/components/responses/403" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" /templates/aliases/{alias}: get: @@ -2979,20 +3070,20 @@ paths: type: string description: Template alias responses: - '200': + "200": description: Successfully queried template by alias content: application/json: schema: - $ref: '#/components/schemas/TemplateAliasResponse' - '400': - $ref: '#/components/responses/400' - '403': - $ref: '#/components/responses/403' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/TemplateAliasResponse" + "400": + $ref: "#/components/responses/400" + "403": + $ref: "#/components/responses/403" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" /nodes: get: @@ -3001,7 +3092,7 @@ paths: security: - AdminTokenAuth: [] responses: - '200': + "200": description: Successfully returned all nodes content: application/json: @@ -3009,11 +3100,11 @@ paths: type: array items: allOf: - - $ref: '#/components/schemas/Node' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + - $ref: "#/components/schemas/Node" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /nodes/{nodeID}: get: @@ -3022,7 +3113,7 @@ paths: security: - AdminTokenAuth: [] parameters: - - $ref: '#/components/parameters/nodeID' + - $ref: "#/components/parameters/nodeID" - in: query name: clusterID description: Identifier of the cluster @@ -3031,39 +3122,39 @@ paths: type: string format: uuid responses: - '200': + "200": description: Successfully returned the node content: application/json: schema: - $ref: '#/components/schemas/NodeDetail' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/NodeDetail" + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" post: description: Change status of a node tags: [admin] security: - AdminTokenAuth: [] parameters: - - $ref: '#/components/parameters/nodeID' + - $ref: "#/components/parameters/nodeID" requestBody: content: application/json: schema: - $ref: '#/components/schemas/NodeStatusChange' + $ref: "#/components/schemas/NodeStatusChange" responses: - '204': + "204": description: The node status was changed successfully - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" /admin/teams/{teamID}/sandboxes/kill: post: @@ -3081,18 +3172,18 @@ paths: format: uuid description: Team ID responses: - '200': + "200": description: Successfully killed sandboxes content: application/json: schema: - $ref: '#/components/schemas/AdminSandboxKillResult' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/AdminSandboxKillResult" + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" /admin/teams/{teamID}/builds/cancel: post: @@ -3110,18 +3201,18 @@ paths: format: uuid description: Team ID responses: - '200': + "200": description: Successfully cancelled builds content: application/json: schema: - $ref: '#/components/schemas/AdminBuildCancelResult' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/AdminBuildCancelResult" + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" /access-tokens: post: @@ -3134,18 +3225,18 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/NewAccessToken' + $ref: "#/components/schemas/NewAccessToken" responses: - '201': + "201": description: Access token created successfully content: application/json: schema: - $ref: '#/components/schemas/CreatedAccessToken' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/CreatedAccessToken" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /access-tokens/{accessTokenID}: delete: @@ -3154,16 +3245,16 @@ paths: security: - Supabase1TokenAuth: [] parameters: - - $ref: '#/components/parameters/accessTokenID' + - $ref: "#/components/parameters/accessTokenID" responses: - '204': + "204": description: Access token deleted successfully - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" /api-keys: get: @@ -3173,18 +3264,18 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] responses: - '200': + "200": description: Successfully returned all team API keys content: application/json: schema: type: array items: - $ref: '#/components/schemas/TeamAPIKey' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/TeamAPIKey" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" post: description: Create a new team API key tags: [api-keys] @@ -3196,18 +3287,18 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/NewTeamAPIKey' + $ref: "#/components/schemas/NewTeamAPIKey" responses: - '201': + "201": description: Team API key created successfully content: application/json: schema: - $ref: '#/components/schemas/CreatedTeamAPIKey' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/CreatedTeamAPIKey" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /api-keys/{apiKeyID}: patch: @@ -3217,22 +3308,22 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/apiKeyID' + - $ref: "#/components/parameters/apiKeyID" requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/UpdateTeamAPIKey' + $ref: "#/components/schemas/UpdateTeamAPIKey" responses: - '200': + "200": description: Team API key updated successfully - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" delete: description: Delete a team API key tags: [api-keys] @@ -3240,16 +3331,16 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/apiKeyID' + - $ref: "#/components/parameters/apiKeyID" responses: - '204': + "204": description: Team API key deleted successfully - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" /volumes: get: @@ -3260,18 +3351,18 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] responses: - '200': + "200": description: Successfully listed all team volumes content: application/json: schema: type: array items: - $ref: '#/components/schemas/Volume' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/Volume" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" post: description: Create a new team volume @@ -3285,20 +3376,20 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/NewVolume' + $ref: "#/components/schemas/NewVolume" responses: - '201': + "201": description: Successfully created a new team volume content: application/json: schema: - $ref: '#/components/schemas/VolumeAndToken' - '400': - $ref: '#/components/responses/400' - '401': - $ref: '#/components/responses/401' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/VolumeAndToken" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" /volumes/{volumeID}: get: @@ -3309,20 +3400,20 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/volumeID' + - $ref: "#/components/parameters/volumeID" responses: - '200': + "200": description: Successfully retrieved a team volume content: application/json: schema: - $ref: '#/components/schemas/VolumeAndToken' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + $ref: "#/components/schemas/VolumeAndToken" + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" delete: description: Delete a team volume @@ -3332,13 +3423,13 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - $ref: '#/components/parameters/volumeID' + - $ref: "#/components/parameters/volumeID" responses: - '204': + "204": description: Successfully deleted a team volume - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '500': - $ref: '#/components/responses/500' + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" \ No newline at end of file