From bed22bda8165625fb8fec3b64dcd628d990149f9 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:06:30 +0200 Subject: [PATCH] docs: Cloud Sandboxes API and SDK (#26168) ## Description Add an experimental Sandboxes API and SDK section alongside Docker Sandboxes under AI and agents, at `/ai/sandboxes-api/`. Cover installation of `@docker/sandboxes`, browser and PAT authentication, and a TypeScript walkthrough that launches the shell kit, runs a command, and deletes the sandbox. Explain kits and image choices with examples, along with compute sizes, quotas, permissions, resource lifecycle, errors, and retries. Link subscription setup and billing to the shared Docker Agentic Platform guide. Preserve the previous `/ai/sandboxes/api/` URLs with redirects. Uses the shared Docker Agentic Platform documentation merged in #26142. Sibling to #26158, which introduces CLI-side Cloud Sandboxes documentation. Include 36 SDK cookbook recipes at `/ai/sandboxes-api/cookbook/` and the public REST API reference at `/reference/api/sandboxes/`. Import the generated export from `docker/sbx-api` commit `60d79c1d` (source `1a7f4026`). Overview and installation remain maintained here. The reference uses the shared API renderer from main, with separate operation and schema pages, streaming and WebSocket details, and a downloadable OpenAPI spec. An API reference link appears in the API/SDK sidebar. A pinned manifest and import script provide the basis for later release automation. Examples are optional in the shared renderer; supplied examples still have to pass schema validation. Requests without body examples use a request-body file with instructions to prepare it. The import copies all selected recipes and the OpenAPI specification byte for byte from the corrected upstream export, without patches or link rewrites. The broken naming recipe remains temporarily excluded. This remains a draft pending validation against the published npm SDK. The imported cookbook contains the TypeScript-only guidance, five-minute request deadlines, kit-readiness explanation, active-policy status requirement, and snapshot-deletion and lifetime-renewal restrictions. Validation: scoped Markdown and Vale checks, full Hugo build, rendered navigation, redirects, and internal links, and TypeScript type-checking against the generated public SDK from `docker/sbx-api` commit `68f214b9`. Import validation also covers all 60 REST operations and 188 schema definitions, 1,198 imported-page links and anchors, desktop/mobile rendering, Markdown output, spec delivery, and a repeat import. The shared renderer tests, strict generation for all five APIs (zero diagnostics), full Hugo build, and verification of 453 HTML/Markdown page pairs and API links pass. No live sandbox execution. The corrected export passes strict generation for all five APIs with zero diagnostics, scoped Markdown/Vale lint, a full Hugo build, and verification of 453 HTML/Markdown page pairs and API links. A repeat import produces identical files. The spec update changes examples only; schemas and operations are unchanged. @netlify /ai/sandboxes-api/ Generated by Codex --------- Co-authored-by: Claude --- COMPONENTS.md | 21 + content/manuals/_index.md | 4 + content/manuals/ai/sandboxes-api/_index.md | 62 + .../ai/sandboxes-api/authentication.md | 154 + content/manuals/ai/sandboxes-api/concepts.md | 200 + .../ai/sandboxes-api/cookbook/_index.md | 62 + ...a-docker-credential-for-cloud-sandboxes.md | 48 + .../cookbook/add-tools-with-kits.md | 151 + .../cookbook/attach-persistent-storage.md | 143 + .../cookbook/clone-a-cloud-sandbox.md | 111 + .../connect-to-cloud-with-a-bearer-token.md | 201 + .../control-what-a-sandbox-can-reach.md | 122 + .../copy-a-file-into-a-cloud-sandbox.md | 116 + .../cookbook/create-your-first-sandbox.md | 160 + .../cookbook/delete-a-cloud-sandbox.md | 64 + .../expose-a-port-from-a-cloud-sandbox.md | 111 + .../find-a-process-you-lost-track-of.md | 113 + .../get-a-stored-secret-into-a-sandbox.md | 100 + .../cookbook/get-an-ssh-certificate.md | 57 + .../cookbook/get-image-pull-urls.md | 45 + .../cookbook/give-a-sandbox-an-mcp-gateway.md | 246 + .../cookbook/handle-errors-and-degradation.md | 106 + .../cookbook/keep-a-cloud-sandbox-running.md | 114 + .../let-a-stopped-sandbox-resume-on-demand.md | 94 + .../cookbook/manage-cloud-secrets.md | 153 + .../cookbook/manage-network-policies.md | 75 + .../cookbook/page-through-and-filter-lists.md | 76 + .../cookbook/read-files-out-of-a-sandbox.md | 212 + .../recover-when-the-endpoint-moves.md | 88 + .../cookbook/register-and-manage-an-image.md | 148 + .../retry-without-creating-duplicates.md | 127 + .../cookbook/run-a-complete-example.md | 101 + ...an-interactive-shell-in-a-cloud-sandbox.md | 220 + ...run-something-that-produces-real-output.md | 111 + .../run-work-across-many-sandboxes.md | 126 + .../cookbook/run-your-first-command.md | 112 + .../cookbook/run-your-own-container-image.md | 61 + .../cookbook/send-values-the-api-accepts.md | 110 + .../cookbook/snapshot-and-fork-a-sandbox.md | 172 + .../cookbook/stop-and-restart-a-sandbox.md | 80 + .../what-your-workload-starts-with.md | 136 + .../cookbook/work-within-the-limits.md | 178 + content/manuals/ai/sandboxes-api/errors.md | 142 + .../manuals/ai/sandboxes-api/get-started.md | 125 + content/manuals/ai/sandboxes-api/install.md | 43 + content/manuals/ai/sandboxes-api/limits.md | 88 + content/manuals/ai/sandboxes-api/reference.md | 13 + content/manuals/ai/sandboxes/_index.md | 3 + content/reference/_index.md | 5 + content/reference/api/_content.gotmpl | 3 + content/reference/api/sandboxes/_index.md | 7 + content/reference/api/sandboxes/api.yaml | 9307 +++++++++++++++++ hack/api-docs/README.md | 4 + hack/api-docs/browser-checks.mjs | 39 +- hack/api-docs/main.go | 26 +- hack/api-docs/main_test.go | 75 + hack/api-docs/model.go | 5 +- hack/api-docs/sources.json | 14 + hack/sandboxes/README.md | 47 + hack/sandboxes/source.json | 43 + hack/sync-sandboxes-docs.py | 50 + .../_partials/api-reference/manual-link.html | 20 +- .../_partials/api-reference/websocket.html | 19 + layouts/_partials/content-default.html | 3 + layouts/_partials/sidebar/sections.html | 13 +- layouts/_shortcodes/recipe-list.html | 17 + layouts/_shortcodes/recipe-list.markdown.md | 3 + layouts/api-docs.html | 56 +- layouts/api-docs.markdown.md | 41 +- 69 files changed, 15059 insertions(+), 43 deletions(-) create mode 100644 content/manuals/ai/sandboxes-api/_index.md create mode 100644 content/manuals/ai/sandboxes-api/authentication.md create mode 100644 content/manuals/ai/sandboxes-api/concepts.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/_index.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/add-a-docker-credential-for-cloud-sandboxes.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/add-tools-with-kits.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/attach-persistent-storage.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/clone-a-cloud-sandbox.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/connect-to-cloud-with-a-bearer-token.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/control-what-a-sandbox-can-reach.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/copy-a-file-into-a-cloud-sandbox.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/create-your-first-sandbox.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/delete-a-cloud-sandbox.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/expose-a-port-from-a-cloud-sandbox.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/find-a-process-you-lost-track-of.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/get-a-stored-secret-into-a-sandbox.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/get-an-ssh-certificate.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/get-image-pull-urls.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/give-a-sandbox-an-mcp-gateway.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/handle-errors-and-degradation.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/keep-a-cloud-sandbox-running.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/let-a-stopped-sandbox-resume-on-demand.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/manage-cloud-secrets.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/manage-network-policies.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/page-through-and-filter-lists.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/read-files-out-of-a-sandbox.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/recover-when-the-endpoint-moves.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/register-and-manage-an-image.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/retry-without-creating-duplicates.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/run-a-complete-example.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/run-an-interactive-shell-in-a-cloud-sandbox.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/run-something-that-produces-real-output.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/run-work-across-many-sandboxes.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/run-your-first-command.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/run-your-own-container-image.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/send-values-the-api-accepts.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/snapshot-and-fork-a-sandbox.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/stop-and-restart-a-sandbox.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/what-your-workload-starts-with.md create mode 100644 content/manuals/ai/sandboxes-api/cookbook/work-within-the-limits.md create mode 100644 content/manuals/ai/sandboxes-api/errors.md create mode 100644 content/manuals/ai/sandboxes-api/get-started.md create mode 100644 content/manuals/ai/sandboxes-api/install.md create mode 100644 content/manuals/ai/sandboxes-api/limits.md create mode 100644 content/manuals/ai/sandboxes-api/reference.md create mode 100644 content/reference/api/sandboxes/_index.md create mode 100644 content/reference/api/sandboxes/api.yaml create mode 100644 hack/sandboxes/README.md create mode 100644 hack/sandboxes/source.json create mode 100644 hack/sync-sandboxes-docs.py create mode 100644 layouts/_partials/api-reference/websocket.html create mode 100644 layouts/_shortcodes/recipe-list.html create mode 100644 layouts/_shortcodes/recipe-list.markdown.md diff --git a/COMPONENTS.md b/COMPONENTS.md index 2ac046ee5f..6e539cbc9c 100644 --- a/COMPONENTS.md +++ b/COMPONENTS.md @@ -86,6 +86,15 @@ params: | time | Estimated time to complete | | prerequisites | Prerequisites or "None" | +### Catalog navigation + +Set `params.sidebar.activeChildrenOnly: true` on a section to show only its +active child in the sidebar. Other children remain available in search and +the sitemap. Sidebar group headings are omitted in this mode. + +For a recipe catalog, set `params.recipeCatalog: true` on the section to add +a **Browse all recipes** link to each recipe page. + ## Shortcodes Shortcodes are reusable components that add rich functionality to your @@ -341,6 +350,18 @@ Create card layouts for organizing content. */>}} ``` +### Recipe lists + +List a section's recipes by their `params.sidebar.group` value: + +```markdown +{{}} +``` + +The list uses each page's sidebar title and description, ordered by page +weight. It renders in two columns on larger screens and one column on smaller +screens. The Markdown output includes the same links and descriptions. + ### Icons Use Material Symbols icons in your content. diff --git a/content/manuals/_index.md b/content/manuals/_index.md index ac65957c0f..98057b510b 100644 --- a/content/manuals/_index.md +++ b/content/manuals/_index.md @@ -24,6 +24,10 @@ params: description: Run AI coding agents in isolated environments. icon: command-line link: /ai/sandboxes/ + - title: Sandboxes API and SDK + description: Create and manage cloud sandboxes from your applications. + icon: code-bracket + link: /ai/sandboxes-api/ - title: MCP Catalog and Toolkit description: Augment your AI workflow with MCP servers. icon: /icons/toolkit.svg diff --git a/content/manuals/ai/sandboxes-api/_index.md b/content/manuals/ai/sandboxes-api/_index.md new file mode 100644 index 0000000000..21389b4fa7 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/_index.md @@ -0,0 +1,62 @@ +--- +title: Docker Sandboxes API and SDK +linkTitle: Sandboxes API and SDK +description: Use the Docker Sandboxes API and TypeScript SDK to manage cloud sandboxes programmatically. +keywords: docker sandboxes API, cloud sandboxes API, sandbox SDK, TypeScript SDK, JavaScript +weight: 15 +params: + apiReferenceBanner: false + sidebar: + group: AI and agents + badge: + color: violet + text: Experimental +aliases: + - /ai/sandboxes/api/ +--- + +> [!NOTE] +> The Docker Sandboxes API and SDK are experimental. Features, interfaces, +> and behavior may change. + +Use the Docker Sandboxes API to create cloud sandboxes, run commands, and +transfer files from your applications and automated workflows. You can also +manage related resources, including images, snapshots, volumes, and secrets. + +To try it, [run your first cloud sandbox](get-started.md) with the TypeScript +SDK. The tutorial uses a bundled kit that supplies a shell environment for +running commands. + +## Activate cloud access + +To use the API or SDK, [activate a Docker Agentic Platform subscription](/manuals/agentic-platform/signup.md#activate-cloud-access). +Use the same Docker account to [authenticate your application](authentication.md). + +Cloud compute is billed separately from your Docker subscription. See +[Billing](/manuals/agentic-platform/signup.md#billing) for details. + +## Choose an interface + +Use the TypeScript SDK in your JavaScript or TypeScript application. The SDK +provides typed requests and responses, waits for sandboxes to start or stop, +and handles file transfers and interactive processes. +See [Install the SDK](install.md) for installation instructions. + +You can also call the REST API directly from any language or HTTP tool. +See the [API reference](/reference/api/sandboxes/latest/) for operations, +request fields, responses, and the downloadable OpenAPI specification. + +To run agents from your terminal, see [Docker Sandboxes](../sandboxes/_index.md). + +## Develop your application + +- [SDK cookbook](cookbook/_index.md): follow examples for processes, files, + storage, networking, and other sandbox operations. +- [API concepts](concepts.md): choose a kit or image, identify resources, and + wait for actions to finish. +- [Authentication and authorization](authentication.md): authenticate requests + and understand which permissions your application needs. +- [Errors and retries](errors.md): handle failures and retry requests without + duplicating work. +- [Compute sizes and limits](limits.md): choose resources and handle account + quotas and request rate limits. diff --git a/content/manuals/ai/sandboxes-api/authentication.md b/content/manuals/ai/sandboxes-api/authentication.md new file mode 100644 index 0000000000..4e09a04cd3 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/authentication.md @@ -0,0 +1,154 @@ +--- +title: Authentication and authorization +description: Authenticate requests to the Docker Sandboxes API with access tokens and learn which permissions your application needs. +keywords: docker sandboxes API authentication, sandbox authorization, bearer token, sandbox permissions, owner scope +weight: 30 +aliases: + - /ai/sandboxes/api/authentication/ +--- + +> [!NOTE] +> The Docker Sandboxes API and SDK are experimental. Features, interfaces, +> and behavior may change. + +Use browser sign-in when running an application interactively, or a personal +access token (PAT) for automation. The SDK obtains short-lived access tokens +and renews them as needed. + +You need an active [Docker Agentic Platform subscription](/manuals/agentic-platform/signup.md#activate-cloud-access). +Authenticate with the Docker account you used to subscribe. + +## Sign in through your browser + +Use the OAuth helper to sign in with your Docker account: + +```typescript +import { oauth, Sandboxes } from '@docker/sandboxes'; + +const auth = oauth({ + onVerification({ verificationUriComplete, verificationUri, userCode }) { + console.log(`Open ${verificationUriComplete ?? verificationUri}`); + console.log(`Verification code: ${userCode}`); + }, +}); +await auth.getAccessToken(); +const client = new Sandboxes({ auth }); +``` + +Open the printed URL and complete sign-in. Browser sign-in supports single +sign-on and two-factor authentication. The `getAccessToken()` call waits for +you to finish before the program continues. + +The SDK keeps credentials in memory and refreshes them while your application +runs. With the default configuration, you sign in again each time you start +the application. SDK sign-in is separate from `docker login` and `sbx login`. + +## Authenticate automation with a PAT + +Use a [personal access token](/manuals/security/access-tokens/personal-access-tokens.md) +for CI jobs and unattended applications. When creating the token, select the +`sandbox:use` permission in your Docker account's personal access token +settings. Registry permissions alone don't grant Cloud Sandboxes access. + +Provide your Docker ID and PAT to the SDK. For example, read them from your +application's environment: + +```typescript +import { pat, Sandboxes } from '@docker/sandboxes'; + +const username = process.env.DOCKER_ID; +const personalAccessToken = process.env.DOCKER_PAT; +if (!username || !personalAccessToken) { + throw new Error('Set DOCKER_ID and DOCKER_PAT'); +} + +const client = new Sandboxes({ + auth: pat({ username, personalAccessToken }), +}); +``` + +The SDK exchanges the PAT for a short-lived access token and repeats the +exchange when needed. If the PAT is invalid or revoked, authentication fails. + +Store the PAT in your CI or application's secret store and keep it out of +source control and logs. + +## Authenticate agents + +An AI agent needs credentials for its model provider in addition to your +Docker sign-in. For example, Claude Code can use an Anthropic API key, and +Codex can use an OpenAI API key. + +Store the provider key as a secret and attach it when creating the sandbox. +For example, with an authenticated `client` and an Anthropic API key in +`providerKey`: + +```typescript +const secret = await client.secrets.create({ + displayName: 'anthropic-key', + serviceType: 'anthropic', + token: { value: providerKey }, +}); + +const sandbox = await client.kits.launchAndWait('claude', { + storage: { secrets: [secret.name] }, +}); +``` + +The secret is attached before the agent runs. Keep the key out of command +arguments, source files, and plain environment variables inside the sandbox. + +## Resource access and permissions + +Your credentials determine which account's resources you can access. Cloud +uses them to identify the account, so leave the optional `parent` field empty +in requests. + +Each request also checks whether you have permission for the action on the +target resource. For example, creating a sandbox requires `sandboxesCreate`, +reading it requires `sandboxesRead`, and deleting it requires +`sandboxesDelete`. + +Account permissions also control access to optional features. See +[Supported Cloud options](concepts.md#choose-supported-cloud-options). + +## Authenticate direct API requests + +If you call the REST API without the SDK, obtain and renew access tokens in +your application. Exchange your Docker ID and a PAT with `sandbox:use` +permission using the [Docker Hub authentication API](/reference/api/hub/latest/operations/AuthCreateAccessToken/): + +```console +$ ACCESS_TOKEN=$(curl --silent --show-error --fail --request POST \ + --url https://hub.docker.com/v2/auth/token \ + --header "Content-Type: application/json" \ + --data '{"identifier":"","secret":""}' \ + | jq -er '.access_token') +``` + +Send the returned access token in the `Authorization` header when calling +`https://connect.docker.com/sandboxes`: + +```http +Authorization: Bearer +``` + +The Sandboxes API accepts the access token returned by the exchange, not the +PAT itself. + +If you manage access tokens but use the SDK for requests, import `bearer` from +`@docker/sandboxes` and configure the client with `auth: bearer(accessToken)`. +Create a client with a fresh token when the previous token expires. + +## Authenticate sandbox requests + +The SDK handles authentication when you run commands or transfer files using +a sandbox's methods. It obtains a token scoped to that sandbox and the +operation, then reuses or renews the token as needed. + +For example, running a command requires `sandboxesExec` and obtaining its +token requires `sandboxesCredential`. Your account must have both permissions. + +If you call a sandbox endpoint directly, use a token issued for that sandbox. +Don't send the Docker Hub token used for management requests to a sandbox +endpoint. See [Management and sandbox endpoints](concepts.md#management-and-sandbox-endpoints). diff --git a/content/manuals/ai/sandboxes-api/concepts.md b/content/manuals/ai/sandboxes-api/concepts.md new file mode 100644 index 0000000000..2ee9555d1c --- /dev/null +++ b/content/manuals/ai/sandboxes-api/concepts.md @@ -0,0 +1,200 @@ +--- +title: Docker Sandboxes API concepts +linkTitle: API concepts +description: Learn how kits and images define a cloud sandbox, how to connect to it, and how to manage resources throughout their lifecycle. +keywords: docker sandboxes API concepts, sandbox kits, sandbox images, cloud sandbox endpoint, API operations +weight: 20 +aliases: + - /ai/sandboxes/api/concepts/ +--- + +> [!NOTE] +> The Docker Sandboxes API and SDK are experimental. Features, interfaces, +> and behavior may change. + +An application uses the Docker Sandboxes API to create sandboxes, connect to +them, and track their state. Choose the environment for your sandbox, then +learn how to work with its resources throughout their lifecycle. + +## Kits and sandbox images + +A sandbox kit defines an environment for an agent or tool, including its +image, setup, network rules, and credential requirements. The SDK bundles a +catalog of kits you can launch by name. Using kits from other sources +requires preparing their content for the API. + +You can also create a sandbox from a container image. Choose the source based +on how much of the environment you want to configure yourself: + +| Source | What it provides | How to create a sandbox | +| --- | --- | --- | +| [Bundled kit](#bundled-kits) | An image reference and configuration included in the SDK's catalog | `client.kits.launch('shell')` | +| Custom kit | An environment defined by a kit you obtain separately | `client.create()` with [prepared kit artifacts](#supply-kit-artifacts) | +| Registry image (`imageRef`) | A container image to use with your own sandbox settings | `client.create({ imageRef: 'ubuntu:24.04', resources: 'small' })` | +| Image resource (`image`) | An image already prepared for Cloud Sandboxes, including its compute settings | `client.create({ image: 'images/' })` | + +The `imageRef` value is an image name in a registry. The `image` value is a +resource name returned by the Sandboxes API. When you use `image`, omit +`resources` because the image resource supplies its compute settings. + +These creation methods don't wait for the sandbox to be running. See +[Wait for an action to finish](#wait-for-an-action-to-finish) before running +commands. + +### Bundled kits + +The npm package includes the following kit definitions and supporting files. +Launch a bundled kit by its short name, such as `shell`: + +| Kit name | Environment | +| --- | --- | +| `shell` | A shell environment for running your own commands | +| `claude` | Claude Code | +| `codex` | Codex | +| `cursor` | Cursor | +| `devin` | Devin | +| `docker-agent` | Docker Agent | +| `gemini` | Gemini CLI | +| `opencode` | OpenCode | + +For example, `client.kits.launchAndWait('shell')` creates a shell sandbox and +waits until it's running. The kit launch helpers default to `small` compute, +with two CPUs and 4 GiB of memory. See [Compute sizes](limits.md#compute-sizes) +to choose a different size. + +Bundled kits are tied to the SDK release. Call `client.kits.list()` to see +the catalog in your installed version. The kit definitions are included in +the npm package, so the SDK doesn't download them from a registry. Cloud +Sandboxes pulls their referenced container images as needed. + +To run an AI agent, provide credentials for its model provider, such as an +Anthropic API key for Claude Code. See +[Authenticate agents](authentication.md#authenticate-agents). The `shell` kit +needs no provider key to run commands. + +## Resource names + +Use a resource's returned `name` to refer to it in later requests. A sandbox +name has the form `sandboxes/`. Pass the complete name, including the +`sandboxes/` prefix, when reading or deleting it. + +The server assigns the name, which stays the same throughout the resource's +lifetime. The optional `displayName` is a label you can change without +changing the resource's identity. + +## Wait for an action to finish + +Wait until a sandbox is running before sending commands to it. In the SDK, +`client.kits.launchAndWait()` creates a bundled kit's sandbox and waits for it +to run. If you use `client.create()` or `client.kits.launch()`, call +`waitUntilRunning()` on the returned sandbox and use the result to run commands. + +For direct API requests, HTTP 202 means the action was accepted and is still +in progress. Read the resource repeatedly until it reaches the state you need. + +Sandbox creation can continue after your client stops waiting. Read the +sandbox again to check its state, and inspect its `failure` field if it has +failed. See [Errors and retries](errors.md) for how to recover. + +Deletion can also take time. The API returns HTTP 202 while the sandbox is +being deleted and HTTP 204 when deletion is complete. After deletion, +authorized reads return `notFound`. + +### Wait for kit setup + +`waitUntilRunning()` and `kits.launchAndWait()` wait for the sandbox to reach +the `running` state. Kit setup commands, such as installing tools or cloning +a repository, can still be running at that point. + +The SDK doesn't provide a helper that waits for all kit setup to finish. If +your application depends on that setup, add a readiness check before starting +its work. What you check depends on the kit and the task—for example, a +completion marker written after a repository clone finishes, or a successful +health check from a service. + +Poll with a delay between checks and a timeout so your application stops +waiting if setup fails. + +## Management and sandbox endpoints + +Creating a sandbox and running a command inside it use different endpoints: + +| Endpoint | Use it to | +| --- | --- | +| Management API at `https://connect.docker.com/sandboxes` | Create, inspect, and delete sandboxes and manage related resources. | +| Sandbox API at the returned `core.endpoint.uri` | Run processes and read or write files inside that sandbox. | + +The SDK builds request URLs from these base URLs. If you make HTTP requests +directly, append the `/v1` route to the base URL, preserving any existing path. +For example, the management route `/v1/sandboxes` becomes +`https://connect.docker.com/sandboxes/v1/sandboxes`. + +Each sandbox endpoint requires a token that grants access to that sandbox. +The SDK obtains this token when you use a sandbox's process or file methods. See +[Authentication and authorization](authentication.md) for details. + +A sandbox's endpoint can change when its runtime changes. Read the sandbox +resource again before reconnecting to get its endpoint. + +## Read all results from a list + +List requests return one page of results at a time. To retrieve the next page, +pass the response's `nextPageToken` as the next request's `pageToken`. Keep the +same page size, filter, and ordering. Continue until `nextPageToken` is empty, +even if a page contains fewer items than you requested. + +The default page size is 25 for Cloud sandbox, image, snapshot, volume, and +secret lists. You can request up to 100 items per page. + +## Choose supported Cloud options + +Cloud supports kits, sandbox timeouts, stored secrets, and volume attachments, +subject to account permissions and feature availability. For example, volume +access must be enabled for your account. An SDK method's presence doesn't +guarantee that your account can use it. + +## Supply kit artifacts + +To use a kit outside the bundled catalog, your application must load and +prepare its content before calling `client.create()`. The npm SDK doesn't +fetch kits from a registry. Its `kits.launch()` and `kits.launchAndWait()` +helpers accept only bundled kit names. + +For example, the [Hermes agent kit](https://hub.docker.com/r/sbx/hermes-agent-kit) +is published as `docker.io/sbx/hermes-agent-kit:latest`. To use it through the +SDK, you need code outside the SDK that loads the kit definition and its +supporting files into the serialized +[v2 artifact format](https://github.com/docker/sbx-kits-contrib/blob/v0.17.0/spec/types.go) +accepted by the API. + +The `kits` array holds the sandbox kit and any mixin kits that add +configuration to it. See [Kits](../sandboxes/customize/kits.md) for how these kinds of +kits work together. The bundled launch helpers prepare this same input for +the kits in their catalog. + +Pass the kit's source reference and prepared artifact bytes to +`client.create()`: + +```typescript +function createFromKit(reference: string, artifactBytes: Uint8Array) { + return client.create({ + resources: 'small', + kits: [ + { + artifact: { + ref: { ref: reference, kind: 'sandbox' }, + inline: artifactBytes, + }, + }, + ], + }); +} +``` + +The `ref` identifies the kit's source. It doesn't trigger a registry pull. +The `inline` value contains the serialized artifact as a `Uint8Array`, +including the kit's file content. Raw `spec.yaml`, ZIP files, and OCI manifests +aren't valid inputs for this field. + +To launch a public kit by registry reference without writing loading code, +use the [Docker Agentic Platform Console](/manuals/agentic-platform/kits.md#run-a-kit-by-reference). diff --git a/content/manuals/ai/sandboxes-api/cookbook/_index.md b/content/manuals/ai/sandboxes-api/cookbook/_index.md new file mode 100644 index 0000000000..90ee0183ae --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/_index.md @@ -0,0 +1,62 @@ +--- +title: Docker Sandboxes SDK cookbook +linkTitle: Cookbook +description: Build agent workflows with the Docker Sandboxes SDK for TypeScript. +keywords: cloud sandboxes, sandboxes sdk, typescript, agent kits +weight: 60 +params: + recipeCatalog: true + sidebar: + activeChildrenOnly: true +--- + +Use these recipes to add file transfers, processes, storage, and other sandbox +operations to your application. Each recipe shows the relevant SDK calls and +an expandable complete TypeScript example. + +Start with [Get started](../get-started.md) to run your first sandbox, or +[install the SDK](../install.md) to use these examples in an existing project. +For HTTP operations and request fields, see the +[API reference](/reference/api/sandboxes/latest/). + +## Get started + +Authenticate and launch a kit before exploring individual SDK operations. + +{{< recipe-list group="Get started" >}} + +## Working in a sandbox + +Use a running sandbox for commands, project files, and web applications. + +{{< recipe-list group="Working in a sandbox" >}} + +## Packaging and state + +Keep an environment or its state for later work. + +{{< recipe-list group="Packaging and state" >}} + +## Security and policy + +Give agents the access they need without embedding credentials in application code. + +{{< recipe-list group="Security and policy" >}} + +## Connect and configure + +Choose a different image, control lifetime, or connect additional storage and clients. + +{{< recipe-list group="Connect and configure" >}} + +## Requests and responses + +Handle request options and failures deliberately. + +{{< recipe-list group="Requests and responses" >}} + +## Long-running work + +Continue work across lost connections or coordinate several sandboxes. + +{{< recipe-list group="Long-running work" >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/add-a-docker-credential-for-cloud-sandboxes.md b/content/manuals/ai/sandboxes-api/cookbook/add-a-docker-credential-for-cloud-sandboxes.md new file mode 100644 index 0000000000..94b267b694 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/add-a-docker-credential-for-cloud-sandboxes.md @@ -0,0 +1,48 @@ +--- +title: "Add a Docker credential for cloud sandboxes" +linkTitle: "Add a Docker credential for cloud sandboxes" +description: "Exchange a Docker OIDC identity token for a credential that the service stores for your sandboxes to use." +keywords: "cloud sandboxes, sandboxes api, add a docker credential for cloud sandboxes" +weight: 404 +params: + sidebar: + group: "Security and policy" +--- + +Store a Docker credential for workloads that need Docker access. This is separate from [signing your application in](connect-to-cloud-with-a-bearer-token.md): the application's access token authenticates SDK calls, while this exchange stores a credential for sandboxes. + +You need an authenticated client and a fresh Docker OpenID Connect identity token for the same identity. The example accepts that identity token; it does not perform the sign-in that issues it. + +## Exchange the token {#1-exchange-the-token} + +Pass the identity token to the identity collection. The service stores the resulting credential as `secrets/docker`, replacing its previous value. It returns no credential material. + +Treat the identity token as single-use. If you lose the response, obtain a new identity token before trying again. Never log either token. + +Use [stored secrets](get-a-stored-secret-into-a-sandbox.md) for other workload credentials. For an automation client that needs to call the SDK, use [PAT authentication](connect-to-cloud-with-a-bearer-token.md) instead of this exchange. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.identity.exchangeDockerCredential({ idToken }); +``` + +
+Complete TypeScript example: credentials/exchange.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function exchangeDockerCredential( + client: Sandboxes, + idToken: string, +) { + return client.identity.exchangeDockerCredential({ idToken }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/add-tools-with-kits.md b/content/manuals/ai/sandboxes-api/cookbook/add-tools-with-kits.md new file mode 100644 index 0000000000..e7119f3ddf --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/add-tools-with-kits.md @@ -0,0 +1,151 @@ +--- +title: "Run agents with kits" +linkTitle: "Run agents with kits" +description: "Discover the available kits, launch an agent sandbox, and run work inside it." +keywords: "cloud sandboxes, sandboxes api, run agents with kits" +weight: 104 +params: + sidebar: + group: "Get started" +--- + +A kit supplies an agent's image and configuration, including tools and the settings they need. Launching a kit gives you a repeatable starting point without assembling those settings in every application. + +Start with an [authenticated client](connect-to-cloud-with-a-bearer-token.md). Agent workloads also need credentials for their model provider. Docker authentication gives access to sandboxes; it does not sign you in to Anthropic, OpenAI, or another agent provider. + +## Discover available kits {#1-discover-available-kits} + +List the kits bundled with your installed SDK. Use an entry's name when launching it. Listing the catalog does not create a sandbox. + +The SDK's launch-by-name helper includes these starting points: + +| Kit name | Environment | +| --- | --- | +| `claude` | Claude Code | +| `codex` | Codex | +| `cursor` | Cursor | +| `devin` | Devin | +| `docker-agent` | Docker Agent | +| `gemini` | Gemini CLI | +| `opencode` | OpenCode | +| `shell` | A shell environment for your own commands | + +Use the catalog result as the list for your installed version. Upgrading the SDK can update the bundled kits. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.kits.list(); +``` + +
+Complete TypeScript example: kits/catalog.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export function listKits(client: Sandboxes) { + return client.kits.list(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Launch a kit {#2-launch-a-kit} + +To run Claude Code, choose `claude` and attach a stored `anthropic` secret containing your Anthropic API key. [Store the provider credential](get-a-stored-secret-into-a-sandbox.md) first, then pass the stored secret's resource name to the launch example. Keep credentials out of command arguments and plain environment values. + +Pass a catalog name, a display name, and any stored secret names the agent needs. The example selects Small (2 vCPUs, 4 GiB) and uses the launch-and-wait helper to return a running sandbox under one deadline. Small is also the default when you omit kit resources. See [compute sizes](work-within-the-limits.md) for the other choices. + +Use `launch` when you want the accepted handle immediately and will wait separately. If the wait fails, inspect the accepted sandbox retained by the error before launching another one. + +`launchAndWait()` waits for the sandbox to reach the running state. A kit can still be installing tools or cloning a repository at that point. The SDK has no helper that waits for all kit setup to finish. + +If your work depends on that setup, check the result it needs before starting. For example, a kit can write a completion marker after cloning a repository, or a service can expose a health check. Poll with a delay between checks and a timeout so failed setup does not leave your application waiting indefinitely. + +The launch helper accepts only names returned by the bundled catalog, not community repository URLs or registry references. Account network policies still apply to kit sandboxes. + +Use `shell` when you need an environment for scripts, builds, or your own executable. To use a container image directly, see [Run your own container image](run-your-own-container-image.md). + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.kits.launchAndWait(kitName, { + displayName, + resources: 'small', + storage: { secrets }, +}); +``` + +
+Complete TypeScript example: kits/launch.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function launchKit( + client: Sandboxes, + kitName: string, + displayName: string, + secrets: string[] = [], +) { + return client.kits.launchAndWait(kitName, { + displayName, + resources: 'small', + storage: { secrets }, + }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Run the agent {#3-run-the-agent} + +Pass the sandbox handle returned by the launch example and your prompt to the run helper. It runs `claude -p` and waits for the response. Launching a kit alone does not submit an agent task. + +The example returns captured output and an exit code. Check both when diagnosing an agent failure. For a long task, [stream output](run-something-that-produces-real-output.md); for a conversation in a terminal, [open an interactive session](run-an-interactive-shell-in-a-cloud-sandbox.md). + +Save the sandbox name if you will resume the work later. [Delete the sandbox](delete-a-cloud-sandbox.md) when you no longer need its files or running processes. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.processes.run( + { args: ['claude', '-p', prompt] }, + { timeoutMs: 300_000 }, +); +``` + +
+Complete TypeScript example: kits/run.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function runKitAgent(sandbox: Sandbox, prompt: string) { + return sandbox.processes.run( + { args: ['claude', '-p', prompt] }, + { timeoutMs: 300_000 }, + ); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Explore the wider kit catalog {#4-explore-the-wider-kit-catalog} + +The [community kit catalog](https://github.com/docker/sbx-kits-contrib) includes agents such as Aider, Amp, Copilot, Kiro, and OpenHands, plus development tools, browser automation, source control, and security scanning. For example, Code Server adds a browser-based editor and Playwright adds browser automation. + +Some kits define a complete sandbox environment; others add tools or configuration to an existing agent environment. These are not all bundled SDK launch targets. See the [Docker kits guide](https://docs.docker.com/ai/sandboxes/customize/kits/) for the catalog's usage instructions. diff --git a/content/manuals/ai/sandboxes-api/cookbook/attach-persistent-storage.md b/content/manuals/ai/sandboxes-api/cookbook/attach-persistent-storage.md new file mode 100644 index 0000000000..49e94c9467 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/attach-persistent-storage.md @@ -0,0 +1,143 @@ +--- +title: "Attach persistent storage" +linkTitle: "Attach persistent storage" +description: "Create a volume, mount it into a new sandbox so its data outlives the sandbox, and delete the volume when you no longer need it." +keywords: "cloud sandboxes, sandboxes api, attach persistent storage" +weight: 505 +params: + sidebar: + group: "Connect and configure" +--- + +Keep project data after a sandbox is deleted by mounting a persistent volume. A volume is an independent resource: deleting a sandbox does not delete the volume. + +Start with an [authenticated client](connect-to-cloud-with-a-bearer-token.md), a managed image from [Register and manage an image](register-and-manage-an-image.md), and an absolute mount path such as `/workspace/data`. + +## Create a volume {#1-create-a-volume} + +Choose a display name and an idempotency key for the create request. Save the returned volume name, such as `volumes/…`. That name identifies the volume in later requests; its display name is only a label. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.volumes.create( + { displayName: name }, + { idempotencyKey: requestId }, +); +``` + +
+Complete TypeScript example: volumes/create.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function createVolume( + client: Sandboxes, + name: string, + requestId: string, +) { + return client.volumes.create( + { displayName: name }, + { idempotencyKey: requestId }, + ); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Mount it in a new sandbox {#2-mount-it-in-a-new-sandbox} + +Supply the volume name and destination path when creating the sandbox. The example accepts a map of volume names to mount paths and waits for the sandbox to run. For Cloud Sandboxes, supply one entry and use exclusive attachment. Volume access must be enabled for your account; memory-snapshot images do not support volume attachment. + +Attachments belong to the sandbox's creation settings. To mount the volume elsewhere, create another sandbox with the attachment. With exclusive attachment, release the first sandbox before attaching the volume to another one. Avoid overlapping mount paths. + +Write a file under the mount path, delete the sandbox, then attach the volume to a new sandbox to read it again. Files outside the mount remain on the sandbox's own disk. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const sandbox = await client.create( + { + displayName: name, + image, + storage: { + volumes: [...mountPaths].map(([volume, target]) => ({ + volume, + target, + })), + }, + }, + { timeoutMs: 300_000, idempotencyKey: requestId }, +); +return sandbox.waitUntilRunning(); +``` + +
+Complete TypeScript example: volumes/attach.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function createWithVolumes( + client: Sandboxes, + name: string, + image: string, + mountPaths: Map, + requestId: string, +) { + const sandbox = await client.create( + { + displayName: name, + image, + storage: { + volumes: [...mountPaths].map(([volume, target]) => ({ + volume, + target, + })), + }, + }, + { timeoutMs: 300_000, idempotencyKey: requestId }, + ); + return sandbox.waitUntilRunning(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Delete the volume {#3-delete-the-volume} + +Delete the sandbox using the volume first, then delete the volume through its handle. If the service reports that it is still in use, wait for sandbox deletion to finish before retrying. + +Deleting the volume permanently removes its contents. Copy out anything you need to keep. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +await volume.delete(); +``` + +
+Complete TypeScript example: volumes/delete.ts + +```typescript +import type { Volume } from '@docker/sandboxes'; + +export async function deleteVolume(volume: Volume) { + await volume.delete(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/clone-a-cloud-sandbox.md b/content/manuals/ai/sandboxes-api/cookbook/clone-a-cloud-sandbox.md new file mode 100644 index 0000000000..7e16965101 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/clone-a-cloud-sandbox.md @@ -0,0 +1,111 @@ +--- +title: "Clone a cloud sandbox" +linkTitle: "Clone a cloud sandbox" +description: "Read how a running sandbox was configured, then create a second sandbox with the same image and environment." +keywords: "cloud sandboxes, sandboxes api, clone a cloud sandbox" +weight: 702 +params: + sidebar: + group: "Long-running work" +--- + +Create another sandbox using selected settings from an existing one. This example copies the image source and environment, not the sandbox's files or running processes. For a saved filesystem or memory state, use a [snapshot](snapshot-and-fork-a-sandbox.md). + +You need an [authenticated client](connect-to-cloud-with-a-bearer-token.md), the source sandbox's resource name, and a display name for the new sandbox. + +## Read the source configuration {#1-read-the-source-configuration} + +Get the source sandbox and inspect its image and environment. Review environment values before copying them; they may contain credentials or settings specific to the original job. + +Do not treat the complete response as a create request. It includes read-only state and connection details. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const sandbox = await client.get(name); +return sandbox.core; +``` + +
+Complete TypeScript example: clone/inspect.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function inspectSandbox(client: Sandboxes, name: string) { + const sandbox = await client.get(name); + return sandbox.core; +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Create a fresh sandbox {#2-create-a-fresh-sandbox} + +The example copies the image source and environment. For a registry image it also copies resource settings. It waits for the new sandbox to run. + +The call returns the new sandbox's handle, ready for running commands or transferring files. + +This is a limited configuration copy, not a complete clone. It does not copy kits, attached policies, secrets, volumes, files, or process state. A copied image alone may not reproduce a kit's setup. For repeated agent environments, launch the same [named kit](add-tools-with-kits.md) with the options your application saved. + +Attach required network policies and credentials before running work in the new sandbox. The source is unchanged, and both sandboxes need their own cleanup. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const copy = await client.create( + { + displayName: name, + ...(core.image + ? { image: core.image, agent: core.agent } + : { + imageRef: core.imageRef!, + resources: core.resources, + }), + environment: core.environment, + }, + { timeoutMs: 300_000, idempotencyKey: requestId }, +); +return copy.waitUntilRunning(); +``` + +
+Complete TypeScript example: clone/rebuild.ts + +```typescript +import type { Sandbox, Sandboxes } from '@docker/sandboxes'; + +export async function rebuildSandbox( + client: Sandboxes, + core: Sandbox['core'], + name: string, + requestId: string, +) { + if (!core.image && !core.imageRef) + throw new TypeError('Source has no image'); + const copy = await client.create( + { + displayName: name, + ...(core.image + ? { image: core.image, agent: core.agent } + : { + imageRef: core.imageRef!, + resources: core.resources, + }), + environment: core.environment, + }, + { timeoutMs: 300_000, idempotencyKey: requestId }, + ); + return copy.waitUntilRunning(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/connect-to-cloud-with-a-bearer-token.md b/content/manuals/ai/sandboxes-api/cookbook/connect-to-cloud-with-a-bearer-token.md new file mode 100644 index 0000000000..4fe671985f --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/connect-to-cloud-with-a-bearer-token.md @@ -0,0 +1,201 @@ +--- +title: "Authenticate to Docker" +linkTitle: "Authenticate to Docker" +description: "Sign in interactively, use a Docker personal access token, or supply an access token from your application." +keywords: "cloud sandboxes, sandboxes api, authenticate to docker" +weight: 101 +params: + sidebar: + group: "Get started" +--- + +Choose an authenticator when your application creates its client. The client obtains a credential when its first request needs one, then uses it for requests to Docker Cloud Sandboxes. You need an [installed SDK](https://docs.docker.com/ai/sandboxes-api/install/) and a Docker account with Cloud Sandboxes access. + +Choose interactive sign-in when running an example yourself. Use a personal access token (PAT) for a service or CI job. If your application already manages Docker access tokens, pass a token or a token provider. + +## Sign in interactively {#1-sign-in-interactively} + +The helper creates a client with an OAuth authenticator. Call an SDK method, such as listing sandboxes, to start sign-in. Open the printed verification URL in your browser and enter the displayed code. Sign in to your Docker account and approve the request. The SDK request proceeds when verification succeeds; a denied or expired request fails. The [complete program](run-a-complete-example.md) shows this flow from start to finish. + +The request that starts sign-in also sets its deadline. Pass `{ timeoutMs: 300_000 }` as that call's request options to give yourself five minutes to sign in and complete the request. + +The SDK holds credentials in memory by default and refreshes the access token as later requests need it, while refresh credentials remain valid. Close the client when finished to release SDK-owned resources. This does not delete sandboxes or revoke Docker sign-in. + +You can pass the same authenticator to several clients to reuse their sign-in. Closing one client leaves the authenticator usable by the others. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const auth = oauth({ + onVerification: ({ verificationUri, userCode }) => { + console.log(`Open ${verificationUri} and enter ${userCode}`); + }, +}); +return new Sandboxes({ auth }); +``` + +
+Complete TypeScript example: cloudauth/login.ts + +```typescript +import { + fileOAuthCredentialStore, + oauth, + Sandboxes, +} from '@docker/sandboxes'; + +export function login() { + const auth = oauth({ + onVerification: ({ verificationUri, userCode }) => { + console.log(`Open ${verificationUri} and enter ${userCode}`); + }, + }); + return new Sandboxes({ auth }); +} + +export function loginWithSavedCredentials(path: string) { + const storedAuth = oauth({ + store: fileOAuthCredentialStore({ path }), + onVerification: ({ verificationUri, userCode }) => { + console.log(`Open ${verificationUri} and enter ${userCode}`); + }, + }); + return new Sandboxes({ auth: storedAuth }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Optional: save credentials between runs {#2-optional-save-credentials-between-runs} + +To avoid signing in each time a Node.js program starts, expand the complete example above and use `loginWithSavedCredentials(path)`. It passes `fileOAuthCredentialStore` to the OAuth authenticator. + +The file store supports Node.js on POSIX systems, not browsers or Windows. Its file contains plaintext access and refresh credentials: keep it out of source control, use a private directory, and do not share it between processes. For a keychain or secret manager, implement `OAuthCredentialStore` with `load` and `save` instead. + +Remove the stored credentials when your application no longer needs them. Your application owns that removal; closing a client does not remove the file or revoke the credentials. + +## Use a personal access token {#3-use-a-personal-access-token} + +Create a [Docker personal access token](https://docs.docker.com/security/access-tokens/) for the Docker account your application will use. Provide your Docker username and PAT through your application's secret manager or environment, then pass them to the SDK's PAT authentication option. + +The SDK exchanges the PAT for a short-lived access token and repeats the exchange when needed. Supply the PAT as a PAT credential, not as an access token or an Authorization header. A revoked or expired PAT requires a replacement credential. + +Managed OAuth and PAT authentication use the default Docker service address. Use a caller-supplied access token or provider when you need to override that address. + +Your PAT needs the `sandbox:use` permission. Select it when you create the token, at `https://app.docker.com/accounts/[username]/settings/personal-access-tokens`. + +Keep the PAT on the machine running your application. Do not put it in a sandbox's environment, source code, or logs. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return new Sandboxes({ + auth: pat({ username, personalAccessToken }), +}); +``` + +
+Complete TypeScript example: cloudauth/pat.ts + +```typescript +import { pat, Sandboxes } from '@docker/sandboxes'; + +export function connectWithPAT( + username: string, + personalAccessToken: string, +) { + return new Sandboxes({ + auth: pat({ username, personalAccessToken }), + }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Supply an access token {#4-supply-an-access-token} + +If you already hold a Docker access-token JWT, pass it as the client's access token. The SDK sends it as a bearer credential. A raw PAT is not an access-token JWT. + +A fixed token has no refresh credential. Once it expires, create a client with a fresh token, or use the provider option below. The example accepts a service URL for applications that need an override; the default is `https://connect.docker.com/sandboxes`. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return new Sandboxes({ + baseUrl: endpoint, + auth: bearer(accessToken), +}); +``` + +
+Complete TypeScript example: cloudauth/connect.ts + +```typescript +import { bearer, Sandboxes } from '@docker/sandboxes'; + +export function connectToCloud(endpoint: string, accessToken: string) { + return new Sandboxes({ + baseUrl: endpoint, + auth: bearer(accessToken), + }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Supply a token provider {#5-supply-a-token-provider} + +Pass a callback that obtains a current Docker access token from your credential system. The callback returns the token string; your application owns its acquisition, storage, and renewal. Do not return an expired token. + +Use one credential source per client. The SDK does not read environment variables or credentials saved by Docker command-line tools automatically. + +Authentication errors mean the credential is missing, rejected, or expired. Sign in again or replace the credential. A permission error means the account cannot perform the requested action; check its Cloud Sandboxes access and resource permissions before retrying. + +When you run commands or transfer files through a sandbox handle, the SDK obtains a credential scoped to that sandbox. You do not need to copy your account token into a second client. + +The client reuses valid sandbox credentials. Concurrent requests share acquisition only within the same management client and for the same resolved Docker credential, sandbox endpoint and permissions. A later request renews the credential shortly before expiry. Renewal happens when needed, not on a five-minute timer, and does not change a request or stream already in progress. + +Docker sign-in is separate from an agent's provider credential. To let an agent call its model provider, follow [Use secrets in a sandbox](get-a-stored-secret-into-a-sandbox.md). + +Next, [run a complete program](run-a-complete-example.md) that signs in and launches a kit. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return new Sandboxes({ + auth: { getAccessToken: tokenProvider }, +}); +``` + +
+Complete TypeScript example: cloudauth/provider.ts + +```typescript +import { Sandboxes, type Authenticator } from '@docker/sandboxes'; + +export function connectWithTokenProvider( + tokenProvider: Authenticator['getAccessToken'], +) { + return new Sandboxes({ + auth: { getAccessToken: tokenProvider }, + }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/control-what-a-sandbox-can-reach.md b/content/manuals/ai/sandboxes-api/cookbook/control-what-a-sandbox-can-reach.md new file mode 100644 index 0000000000..9d75c4d910 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/control-what-a-sandbox-can-reach.md @@ -0,0 +1,122 @@ +--- +title: "Control what a sandbox can reach" +linkTitle: "Control what a sandbox can reach" +description: "Attach your account's network policies to a new sandbox, read the policy the service enforces, and review which destinations it allowed or blocked." +keywords: "cloud sandboxes, sandboxes api, control what a sandbox can reach" +weight: 401 +params: + sidebar: + group: "Security and policy" +--- + +Control which destinations an agent or command can reach. A sandbox's effective policy combines account rules with the policies attached to it. + +You need an [authenticated client](connect-to-cloud-with-a-bearer-token.md), a managed image, and policy IDs from your account. Create policies in the Console or with [Manage network policies](manage-network-policies.md). + +## Attach policies at creation {#1-attach-policies-at-creation} + +Pass the policy IDs when creating the sandbox. The example waits until the sandbox is running and returns its handle. + +Attachments refer to existing policies; they do not contain policy definitions. Keep the IDs distinct from display names. Attached policies restrict access alongside account policy, so they cannot grant access that your account denies. + +Include every destination the workload needs, including its model provider and package registries. A kit's network requirements do not override account restrictions. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const sandbox = await client.create( + { displayName: name, image, network: { policyIds } }, + { timeoutMs: 300_000, idempotencyKey: requestId }, +); +return sandbox.waitUntilRunning(); +``` + +
+Complete TypeScript example: netpolicy/reference.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function createWithPolicyIds( + client: Sandboxes, + name: string, + image: string, + policyIds: string[], + requestId: string, +) { + const sandbox = await client.create( + { displayName: name, image, network: { policyIds } }, + { timeoutMs: 300_000, idempotencyKey: requestId }, + ); + return sandbox.waitUntilRunning(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Inspect the rules in force {#2-inspect-the-rules-in-force} + +Read the sandbox's effective policy before troubleshooting an application timeout. The result includes combined allow and deny rules, their origins, and an exact policy representation. + +The effective view is the useful starting point for questions such as “can this sandbox reach api.anthropic.com?” Check the default mode as well as matching rules. A deny-by-default policy needs an explicit allowance for the destination. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.networkPolicies.get(); +``` + +
+Complete TypeScript example: netpolicy/effective.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function getNetworkPolicies(sandbox: Sandbox) { + return sandbox.networkPolicies.get(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Check allowed and blocked traffic {#3-check-allowed-and-blocked-traffic} + +List policy log entries for the sandbox, using `domain=network` to select network decisions. The example follows pagination and returns all matching entries. + +Use each entry's destination, decision, reason, and count to identify blocked dependencies. An empty result means no matching entries were returned; it does not prove that a destination is reachable. The log records recent activity, not a permanent audit history. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.networkPolicies.logs.all({ filter }).collect(); +``` + +
+Complete TypeScript example: netpolicy/logs.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function listPolicyLogEntries( + client: Sandboxes, + sandboxName: string, + filter: string, +) { + const sandbox = await client.get(sandboxName); + return sandbox.networkPolicies.logs.all({ filter }).collect(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/copy-a-file-into-a-cloud-sandbox.md b/content/manuals/ai/sandboxes-api/cookbook/copy-a-file-into-a-cloud-sandbox.md new file mode 100644 index 0000000000..fec1e7333b --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/copy-a-file-into-a-cloud-sandbox.md @@ -0,0 +1,116 @@ +--- +title: "Copy a file into a cloud sandbox" +linkTitle: "Copy a file into a cloud sandbox" +description: "Create a directory inside a running cloud sandbox, write one file into it from memory, and read the size the write reports back." +keywords: "cloud sandboxes, sandboxes api, copy a file into a cloud sandbox" +weight: 202 +params: + sidebar: + group: "Working in a sandbox" +--- + +Send project files, scripts, or input data to a running sandbox. Use its file collection instead of putting large file contents into command arguments. + +You need a sandbox handle from [your first sandbox](create-your-first-sandbox.md) and an absolute destination path. The paths in these examples are inside the sandbox, not on your computer. + +## Create the destination directory {#1-create-the-destination-directory} + +Create the destination directory with the desired permission mode. The example also creates missing parent directories. + +Choose the narrowest permissions the workload needs. Remember that a file's contents and its executable permission are separate settings. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +await sandbox.files.mkdir(path, { mode, parents: true }); +``` + +
+Complete TypeScript example: upload/mkdir.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function makeDirectory( + sandbox: Sandbox, + path: string, + mode: number, +) { + await sandbox.files.mkdir(path, { mode, parents: true }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Write a small text file {#2-write-a-small-text-file} + +Use the write helper for a configuration file or short script already held as a string. Pass its destination path and content. For binary data or streamed transfers, use upload instead. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.files.write(path, content); +``` + +
+Complete TypeScript example: upload/write.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function writeFile( + sandbox: Sandbox, + path: string, + content: string, +) { + return sandbox.files.write(path, content); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Upload file content {#3-upload-file-content} + +Upload bytes to the destination and read its metadata afterward. The example accepts content from your application; read a local file first if that is your source. + +Choose a file mode explicitly when executable or restricted permissions matter. A failed upload may have written some data. Inspect the destination before retrying if replacing its contents would be unsafe. + +Next, [run a command](run-your-first-command.md) that reads the file, or [read it back](read-files-out-of-a-sandbox.md) to verify the content. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +await sandbox.files.upload(path, content, { mode }); +return (await sandbox.files.stat(path)).info; +``` + +
+Complete TypeScript example: upload/upload.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function uploadFile( + sandbox: Sandbox, + path: string, + content: Uint8Array, + mode: number, +) { + await sandbox.files.upload(path, content, { mode }); + return (await sandbox.files.stat(path)).info; +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/create-your-first-sandbox.md b/content/manuals/ai/sandboxes-api/cookbook/create-your-first-sandbox.md new file mode 100644 index 0000000000..7e156b08fc --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/create-your-first-sandbox.md @@ -0,0 +1,160 @@ +--- +title: "Create your first sandbox" +linkTitle: "Create your first sandbox" +description: "Launch a kit, run a command in its sandbox, and inspect the result." +keywords: "cloud sandboxes, sandboxes api, create your first sandbox" +weight: 103 +params: + sidebar: + group: "Get started" +--- + +Create a sandbox from a kit and run a command inside it. A kit bundles an image and configuration for a tool or agent. The `shell` kit is a useful first choice because a greeting command needs no model-provider credential. + +[Install an SDK](https://docs.docker.com/ai/sandboxes-api/install/) and [authenticate to Docker](connect-to-cloud-with-a-bearer-token.md) first. Pass the authenticated client to this example along with a kit name and command. + +## Launch a kit and run a command {#1-launch-a-kit-and-run-a-command} + +Choose `shell` as the kit name and pass `['echo', 'Hello from Docker Sandboxes']` as the command. The example requests 2 CPUs and 4096 MiB of memory. See [compute sizes and account limits](work-within-the-limits.md) when sizing other workloads. + +The example launches the kit, waits until the sandbox is running, and runs the command through the sandbox's process collection. The SDK handles the connection and its sandbox-scoped credential. + +A successful greeting returns `Hello from Docker Sandboxes` in `stdout` and exit code zero. See [Run your first command](run-your-first-command.md) for argument arrays, shell syntax, and interpreting command results. + +Keep the returned sandbox name. This example leaves the sandbox available for the next guides. [Delete it](delete-a-cloud-sandbox.md) when you finish; closing the SDK client does not delete it. If a wait times out after creation was accepted, the sandbox can still exist. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const created = await client.kits.launch( + kitName, + { resources: { cpus: 2, memoryMib: 4096 } }, + { timeoutMs, signal }, +); +const sandbox = await created.waitUntilRunning({ timeoutMs, signal }); +const result = await sandbox.processes.run( + { args }, + { timeoutMs, signal }, +); +return { sandbox, result }; +``` + +
+Complete TypeScript example: quickstart/run.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function createAndRun( + client: Sandboxes, + kitName: string, + args: string[], + timeoutMs = 300_000, +) { + const signal = AbortSignal.timeout(timeoutMs); + const created = await client.kits.launch( + kitName, + { resources: { cpus: 2, memoryMib: 4096 } }, + { timeoutMs, signal }, + ); + const sandbox = await created.waitUntilRunning({ timeoutMs, signal }); + const result = await sandbox.processes.run( + { args }, + { timeoutMs, signal }, + ); + return { sandbox, result }; +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Clean up a temporary sandbox automatically {#2-clean-up-a-temporary-sandbox-automatically} + +For a single task, the scoped sandbox helper creates a sandbox, runs your callback, and attempts deletion afterward, including when the callback fails. This example explicitly stops the sandbox and waits for that transition before the helper attempts deletion. Supply the command arguments and creation options appropriate for the task; for a published image, set its reference and resources. + +Cleanup has its own deadline. If it fails, inspect the workflow error and the retained sandbox identity so you can finish cleanup. Do not treat a client timeout as confirmation that the sandbox was deleted. + +Next, [choose an agent kit](add-tools-with-kits.md) or [work with processes](run-your-first-command.md). + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.withSandbox(options, async (sandbox) => { + const outcome = await sandbox.processes + .run({ args }, { timeoutMs: 300_000 }) + .then( + (value) => ({ value }), + (error: unknown) => ({ error }), + ); + try { + const cleanup = { signal: AbortSignal.timeout(30_000) }; + const current = await client.get(sandbox.name, cleanup); + if (current.uid !== sandbox.uid) + throw new Error( + 'Sandbox identity changed; refusing to stop a replacement', + ); + const stopped = await current.stop(cleanup); + await stopped.waitUntilStopped(cleanup); + } catch (error) { + if ('error' in outcome) + throw new AggregateError( + [outcome.error, error], + 'Command and stop both failed', + ); + throw error; + } + if ('error' in outcome) throw outcome.error; + return outcome.value; +}); +``` + +
+Complete TypeScript example: quickstart/scoped.ts + +```typescript +import type { ClientCreateOptions, Sandboxes } from '@docker/sandboxes'; + +export async function runTemporary( + client: Sandboxes, + options: ClientCreateOptions, + args: string[], +) { + return client.withSandbox(options, async (sandbox) => { + const outcome = await sandbox.processes + .run({ args }, { timeoutMs: 300_000 }) + .then( + (value) => ({ value }), + (error: unknown) => ({ error }), + ); + try { + const cleanup = { signal: AbortSignal.timeout(30_000) }; + const current = await client.get(sandbox.name, cleanup); + if (current.uid !== sandbox.uid) + throw new Error( + 'Sandbox identity changed; refusing to stop a replacement', + ); + const stopped = await current.stop(cleanup); + await stopped.waitUntilStopped(cleanup); + } catch (error) { + if ('error' in outcome) + throw new AggregateError( + [outcome.error, error], + 'Command and stop both failed', + ); + throw error; + } + if ('error' in outcome) throw outcome.error; + return outcome.value; + }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/delete-a-cloud-sandbox.md b/content/manuals/ai/sandboxes-api/cookbook/delete-a-cloud-sandbox.md new file mode 100644 index 0000000000..274de2e9e4 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/delete-a-cloud-sandbox.md @@ -0,0 +1,64 @@ +--- +title: "Delete a cloud sandbox" +linkTitle: "Delete a cloud sandbox" +description: "Delete a sandbox you have read back and wait until it is gone, with the option to override a refusal based on its current state." +keywords: "cloud sandboxes, sandboxes api, delete a cloud sandbox" +weight: 106 +params: + sidebar: + group: "Get started" +--- + +Delete a sandbox when its work is finished. Deletion removes its processes and sandbox-local files, so download results or save a snapshot first. + +Use a current sandbox handle from creation or from reading its resource name. Closing the SDK client does not delete the sandbox. + +## Delete and wait {#1-delete-and-wait} + +Delete through the sandbox handle, then wait until the sandbox is absent. Deletion can terminate running work. If you choose to [stop the sandbox](stop-and-restart-a-sandbox.md) first, wait for that transition to finish before deleting it: deletion during a stop or resume can be refused. + +The example takes an idempotency key and a wait deadline. Its optional force setting requests deletion when the service would otherwise refuse the sandbox's state. It does not guarantee that every state can be deleted immediately; handle a refusal rather than assuming cleanup succeeded. + +The handle supplies the resource version it read. When an operation returns an updated handle, keep that handle for your next operation; older handles do not update themselves. + +If deletion fails because the resource version changed, call `refresh()`. Inspect the returned handle and decide whether deletion is still appropriate. Refreshing does not change the original handle, so call delete on the returned one. If you supply idempotency keys, use a new key: deleting the refreshed version is a new logical operation, not a retry of the refused request. + +If the wait times out, read the sandbox again to check its state. Keep its name so you can retry cleanup if needed. Repeating a completed deletion is safe. + +Separate resources such as volumes, snapshots, and stored secrets have their own lifetimes. Delete those only when nothing still needs them. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const deleting = await sandbox.delete( + { force }, + { idempotencyKey: requestId }, +); +if (deleting) await deleting.waitUntilDeleted({ timeoutMs }); +``` + +
+Complete TypeScript example: removal/delete.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function deleteSandbox( + sandbox: Sandbox, + requestId: string, + force = false, + timeoutMs = 300_000, +) { + const deleting = await sandbox.delete( + { force }, + { idempotencyKey: requestId }, + ); + if (deleting) await deleting.waitUntilDeleted({ timeoutMs }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/expose-a-port-from-a-cloud-sandbox.md b/content/manuals/ai/sandboxes-api/cookbook/expose-a-port-from-a-cloud-sandbox.md new file mode 100644 index 0000000000..35f3ae61c9 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/expose-a-port-from-a-cloud-sandbox.md @@ -0,0 +1,111 @@ +--- +title: "Expose a port from a cloud sandbox" +linkTitle: "Expose a port from a cloud sandbox" +description: "Publish a TCP port that a program inside a cloud sandbox listens on, read back the URL that reaches it, list the sandbox's published ports, and withdraw the publication." +keywords: "cloud sandboxes, sandboxes api, expose a port from a cloud sandbox" +weight: 204 +params: + sidebar: + group: "Working in a sandbox" +--- + +Reach a web application running inside a sandbox through an HTTPS URL. Publishing creates a route to the application; it does not start the application. + +You need an [authenticated client](connect-to-cloud-with-a-bearer-token.md), the sandbox name, and an HTTP application listening on the port you will publish. Bind the application to an address reachable inside the sandbox, such as `0.0.0.0`. + +## Publish the application's port {#1-publish-the-application-s-port} + +Supply the sandbox name and the application's port number. The example uses TCP. Use the URL returned by the service rather than constructing a hostname. + +This is HTTP application access, not a general-purpose TCP tunnel. Docker account authentication does not protect the application URL. Configure authentication in your application before exposing sensitive data, and never send your Docker account token to that URL. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.ports.create( + { number, protocol: 'tcp' }, + { idempotencyKey: requestId }, +); +``` + +
+Complete TypeScript example: ports/publish.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function createPort( + client: Sandboxes, + sandboxName: string, + number: number, + requestId: string, +) { + const sandbox = await client.get(sandboxName); + return sandbox.ports.create( + { number, protocol: 'tcp' }, + { idempotencyKey: requestId }, + ); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## List published ports {#2-list-published-ports} + +Read the sandbox's port collection to find existing publications and their URLs. Check the application's readiness separately: a published route does not prove the application has started. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return (await sandbox.ports.list()).published ?? []; +``` + +
+Complete TypeScript example: ports/list.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function listPorts(sandbox: Sandbox) { + return (await sandbox.ports.list()).published ?? []; +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Withdraw a publication {#3-withdraw-a-publication} + +Delete the published port when access is no longer needed. This removes the route but leaves the application process running. + +Stopping the sandbox does not remove its port publications. Deleting the sandbox does. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +await port.delete(); +``` + +
+Complete TypeScript example: ports/unpublish.ts + +```typescript +import type { Port } from '@docker/sandboxes'; + +export async function deletePort(port: Port) { + await port.delete(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/find-a-process-you-lost-track-of.md b/content/manuals/ai/sandboxes-api/cookbook/find-a-process-you-lost-track-of.md new file mode 100644 index 0000000000..f61117584b --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/find-a-process-you-lost-track-of.md @@ -0,0 +1,113 @@ +--- +title: "Find a process you lost track of" +linkTitle: "Find a process you lost track of" +description: "Look up a running process by the session tag you gave it when you created it, then pick up its output from the resume point its own report publishes." +keywords: "cloud sandboxes, sandboxes api, find a process you lost track of" +weight: 701 +params: + sidebar: + group: "Long-running work" +--- + +Reconnect to work after losing a process connection. Starting a new command can duplicate effects, so first look for the process you already started. + +Connections opened through TypeScript process handles recover from temporary disconnects while you consume output. They reconnect to the same process and resume after the last delivered chunk, with a 30-second budget per recovery episode. They never restart the command or replay terminal input. Raw streams require explicit reconnection. Use this guide when automatic recovery stops, you close a connection, or your application restarts. + +You need a current sandbox handle and a session tag that your application assigned when it started the process. A session tag is searchable metadata, not the process's resource name. + +## Find the running session {#1-find-the-running-session} + +List processes filtered by session and running state. The example follows pagination. If multiple processes match, choose the intended resource by its saved name or metadata rather than attaching to an arbitrary result. + +An empty list means no running process matched. It does not prove that the original command never started: it may have exited. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.processes + .all({ filter: `session=${session},state=running` }) + .collect(); +``` + +
+Complete TypeScript example: reconnect/list.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function findSession(sandbox: Sandbox, session: string) { + return sandbox.processes + .all({ filter: `session=${session},state=running` }) + .collect(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Reconnect to its output {#2-reconnect-to-its-output} + +Connect to the selected process handle and consume output events. The example resumes from the last sequence reported by that handle. + +For delivery to another system, keep your own cursor for the last chunk that system actually handled. The latest sequence reported by the process may be ahead of that cursor. Use [explicit output resumption](run-an-interactive-shell-in-a-cloud-sandbox.md) when you need to replay from your saved position. + +Close the connection when finished. Keep the process name if the connection drops again; do not replace reconnection with a second process start. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const connection = await process.connect({ + resumeFrom: process.lastStreamSequence, +}); +let lastSequence = BigInt(process.lastStreamSequence ?? '0'); +try { + for await (const event of connection) { + if (event.type === 'chunk') + write( + event.data ?? new Uint8Array(), + (lastSequence = BigInt(event.streamSequence ?? '0')), + ); + } + return lastSequence; +} finally { + await connection.close(); +} +``` + +
+Complete TypeScript example: reconnect/attach.ts + +```typescript +import type { Process } from '@docker/sandboxes'; + +export async function rejoinProcess( + process: Process, + write: (bytes: Uint8Array, sequence: bigint) => void, +) { + const connection = await process.connect({ + resumeFrom: process.lastStreamSequence, + }); + let lastSequence = BigInt(process.lastStreamSequence ?? '0'); + try { + for await (const event of connection) { + if (event.type === 'chunk') + write( + event.data ?? new Uint8Array(), + (lastSequence = BigInt(event.streamSequence ?? '0')), + ); + } + return lastSequence; + } finally { + await connection.close(); + } +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/get-a-stored-secret-into-a-sandbox.md b/content/manuals/ai/sandboxes-api/cookbook/get-a-stored-secret-into-a-sandbox.md new file mode 100644 index 0000000000..5e2ae02527 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/get-a-stored-secret-into-a-sandbox.md @@ -0,0 +1,100 @@ +--- +title: "Get a stored secret into a sandbox" +linkTitle: "Get a stored secret into a sandbox" +description: "Store a credential once, then pass its resource name when creating a sandbox so the sandbox starts with that secret attached." +keywords: "cloud sandboxes, sandboxes api, get a stored secret into a sandbox" +weight: 405 +params: + sidebar: + group: "Security and policy" +--- + +Give an agent access to its provider without placing the real credential in its command arguments or source files. Store the credential once, then attach its secret name when creating each sandbox. + +Start with an [authenticated client](connect-to-cloud-with-a-bearer-token.md). Obtain the provider credential from that provider and read it from your application's secret manager. For Claude Code, use an Anthropic API key and service type `anthropic`; the Docker login token is not an Anthropic key. + +## Store the provider credential {#1-store-the-provider-credential} + +Supply a display name, service type, token value, and an idempotency key. Save the returned secret name. The response contains metadata, never the stored token. + +Choose the service type that matches the workload's provider. A token for one provider does not authenticate another agent. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.secrets.create( + { displayName: name, serviceType, token: { value: token } }, + { idempotencyKey: requestId }, +); +``` + +
+Complete TypeScript example: credinject/store.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function storeSecret( + client: Sandboxes, + name: string, + serviceType: string, + token: string, + requestId: string, +) { + return client.secrets.create( + { displayName: name, serviceType, token: { value: token } }, + { idempotencyKey: requestId }, + ); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Attach the secret at creation {#2-attach-the-secret-at-creation} + +Put the secret name in the sandbox's storage options. This example uses a managed image; the same storage options can be passed when [launching a named kit](add-tools-with-kits.md). + +Attach the secret when creating the sandbox, before starting the agent. Passing a secret's display name instead of its resource name will not select it. Keep the real token on the client side of the storage step rather than duplicating it in environment variables. + +The SDK returns a handle after the example waits for the sandbox to run. Use that handle to run the agent. To replace or delete the credential later, follow [Manage cloud secrets](manage-cloud-secrets.md). + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const sandbox = await client.create( + { displayName: name, image, storage: { secrets: secretNames } }, + { timeoutMs: 300_000, idempotencyKey: requestId }, +); +return sandbox.waitUntilRunning(); +``` + +
+Complete TypeScript example: credinject/attach.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function createWithSecrets( + client: Sandboxes, + name: string, + image: string, + secretNames: string[], + requestId: string, +) { + const sandbox = await client.create( + { displayName: name, image, storage: { secrets: secretNames } }, + { timeoutMs: 300_000, idempotencyKey: requestId }, + ); + return sandbox.waitUntilRunning(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/get-an-ssh-certificate.md b/content/manuals/ai/sandboxes-api/cookbook/get-an-ssh-certificate.md new file mode 100644 index 0000000000..32eba1784c --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/get-an-ssh-certificate.md @@ -0,0 +1,57 @@ +--- +title: "Get an SSH certificate" +linkTitle: "Get an SSH certificate" +description: "Have the service sign your SSH public key for one sandbox and return the host, port, username, and host keys your SSH client needs." +keywords: "cloud sandboxes, sandboxes api, get an ssh certificate" +weight: 506 +params: + sidebar: + group: "Connect and configure" +--- + +Request a short-lived SSH certificate for access to a sandbox. Keep the private key on your machine; send only the public key to Docker. + +You need an [authenticated client](connect-to-cloud-with-a-bearer-token.md), a sandbox name, and an SSH public key. Generate a key pair with your SSH tooling if you do not have one. + +## Request the certificate {#1-request-the-certificate} + +Pass the sandbox name and public key. Save the returned certificate using your SSH client's expected certificate-file format, alongside the matching private key. + +The response supplies the connection details for the sandbox. Use them rather than guessing a hostname or port. The example returns those details; it does not start an SSH process. + +A certificate expires. Request a new one when needed, and keep the private key and certificate out of your repository. For programmatic command execution without an SSH client, use [processes](run-your-first-command.md). + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.ssh.issueCertificate({ + publicKey, + ttlMs: lifeSeconds * 1_000, +}); +``` + +
+Complete TypeScript example: ssh/certificate.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function issueSSHCert( + client: Sandboxes, + sandboxName: string, + publicKey: string, + lifeSeconds: number, +) { + const sandbox = await client.get(sandboxName); + return sandbox.ssh.issueCertificate({ + publicKey, + ttlMs: lifeSeconds * 1_000, + }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/get-image-pull-urls.md b/content/manuals/ai/sandboxes-api/cookbook/get-image-pull-urls.md new file mode 100644 index 0000000000..182772b83a --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/get-image-pull-urls.md @@ -0,0 +1,45 @@ +--- +title: "Get image pull URLs" +linkTitle: "Get image pull URLs" +description: "Get the manifest details and short-lived download URLs for a managed image so a registry tool can pull its contents." +keywords: "cloud sandboxes, sandboxes api, get image pull urls" +weight: 507 +params: + sidebar: + group: "Connect and configure" +--- + +Obtain the registry references needed to pull a managed image with an OCI-compatible tool. This is useful when another part of your workflow needs the image content outside a sandbox. + +You need an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and the image name returned by [image registration](register-and-manage-an-image.md). + +## Get the pull information {#1-get-the-pull-information} + +Read the image's pull specification. The result identifies its manifest digest and image references. + +Pass those references to your OCI client. This example only retrieves the pull information; it does not download layers or authenticate a separate registry client. Treat any returned access information as sensitive, and do not publish it in logs. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const image = await client.images.get(name); +return image.getPullSpec(); +``` + +
+Complete TypeScript example: pullspec/spec.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function getImagePullSpec(client: Sandboxes, name: string) { + const image = await client.images.get(name); + return image.getPullSpec(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/give-a-sandbox-an-mcp-gateway.md b/content/manuals/ai/sandboxes-api/cookbook/give-a-sandbox-an-mcp-gateway.md new file mode 100644 index 0000000000..79d104a367 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/give-a-sandbox-an-mcp-gateway.md @@ -0,0 +1,246 @@ +--- +title: "Give a sandbox an MCP gateway" +linkTitle: "Give a sandbox an MCP gateway" +description: "Configure MCP tools when launching a kit, then inspect, authorize, and manage the gateway." +keywords: "cloud sandboxes, sandboxes api, give a sandbox an mcp gateway" +weight: 403 +params: + sidebar: + group: "Security and policy" +--- + +Give an agent access to external tools through MCP, the Model Context Protocol. A gateway presents several tool servers through one endpoint. + +Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and server IDs available to your account. Choose servers from the [Docker MCP Catalog](https://docs.docker.com/ai/mcp-catalog-and-toolkit/catalog/) and use their catalog IDs, not display names invented by your application. Provider sign-in and permission to use each tool may be required. + +## Configure MCP when launching a kit {#1-configure-mcp-when-launching-a-kit} + +Supply the server IDs in the kit's MCP options when creating the sandbox. This makes gateway configuration available when the agent starts. Attach any model-provider secrets the agent needs separately. + +Use this creation-time path for Cloud Sandboxes. Adding a gateway after creation can be rejected for a governed sandbox; recreating it with MCP configured avoids that limitation. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const sandbox = await client.kits.launch( + kitName, + { + resources: { cpus: 2, memoryMib: 4096 }, + mcp: { servers, static: true }, + storage: { secrets }, + }, + { timeoutMs: 300_000 }, +); +return sandbox.waitUntilRunning(); +``` + +
+Complete TypeScript example: mcp/create.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function launchMcpKit( + client: Sandboxes, + kitName: string, + servers: string[], + secrets: string[] = [], +) { + const sandbox = await client.kits.launch( + kitName, + { + resources: { cpus: 2, memoryMib: 4096 }, + mcp: { servers, static: true }, + storage: { secrets }, + }, + { timeoutMs: 300_000 }, + ); + return sandbox.waitUntilRunning(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Alternatively, start a gateway after creation {#2-alternatively-start-a-gateway-after-creation} + +For an existing sandbox that supports gateway creation, use its MCP collection and wait until the gateway is ready. The sandbox must already have the required Docker credential. If startup is refused, do not assume that retrying will add missing credentials or change its policy configuration. + +Inspect the returned server list and skipped-server reasons. A ready gateway does not guarantee that every requested server was accepted. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const gateway = await sandbox.mcp.start({ servers, static: true }); +return gateway.waitUntilReady({ timeoutMs: 120_000 }); +``` + +
+Complete TypeScript example: mcp/start.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function startMcpGateway( + client: Sandboxes, + name: string, + servers: string[], +) { + const sandbox = await client.get(name.replace(/\/mcp-gateway$/, '')); + const gateway = await sandbox.mcp.start({ servers, static: true }); + return gateway.waitUntilReady({ timeoutMs: 120_000 }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Read the gateway address {#3-read-the-gateway-address} + +Get the gateway and use its returned URL only when it is ready. The example checks readiness before returning the URL. + +Keep gateway credentials private. The gateway's address and the published URL of an application inside the sandbox are different endpoints. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const gateway = await sandbox.mcp.get(); +if (gateway.state !== 'ready' || !gateway.url) + throw new Error('MCP gateway is not ready'); +return gateway.url; +``` + +
+Complete TypeScript example: mcp/read.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function readGatewayUrl(client: Sandboxes, name: string) { + const sandbox = await client.get(name.replace(/\/mcp-gateway$/, '')); + const gateway = await sandbox.mcp.get(); + if (gateway.state !== 'ready' || !gateway.url) + throw new Error('MCP gateway is not ready'); + return gateway.url; +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Add a catalog server {#4-add-a-catalog-server} + +Add a server to a ready, writable gateway. Adding a server already present does not add a second copy. A shared gateway attached by URL cannot be modified through this sandbox. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +await sandbox.mcp.servers.add({ server }); +``` + +
+Complete TypeScript example: mcp/add.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function addMcpGatewayServer( + client: Sandboxes, + name: string, + server: string, +) { + const sandbox = await client.get(name.replace(/\/mcp-gateway$/, '')); + await sandbox.mcp.servers.add({ server }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Complete a server's sign-in {#5-complete-a-server-s-sign-in} + +Start authorization for a server that requires user sign-in. Present its authorization URL to the user, then read the authorization resource to check progress. The example performs the start and read calls; your application decides how to display and poll the flow. + +Only an authorized result means the credential is ready. Keep the authorization identity so a later attempt is not mistaken for completion of an earlier one. Request reauthorization only when you intend a new sign-in. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.mcp.authorizations.authorize({ + server: name, + forceReauth, +}); +``` + +
+Complete TypeScript example: mcp/authorize.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function authorizeMcpServer( + client: Sandboxes, + name: string, + forceReauth: boolean, +) { + return client.mcp.authorizations.authorize({ + server: name, + forceReauth, + }); +} + +export async function getMcpAuthorization( + client: Sandboxes, + name: string, +) { + return client.mcp.authorizations.get(name); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Stop the gateway {#6-stop-the-gateway} + +Stop the gateway when the sandbox no longer needs its tools. This does not delete the sandbox. Detaching from a shared gateway does not remove that gateway for its other users. + +Existing agent processes may still hold old connection settings. Plan their restart or reconfiguration when changing the gateway. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +await sandbox.mcp.stop(); +``` + +
+Complete TypeScript example: mcp/stop.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function stopMcpGateway(client: Sandboxes, name: string) { + const sandbox = await client.get(name.replace(/\/mcp-gateway$/, '')); + await sandbox.mcp.stop(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/handle-errors-and-degradation.md b/content/manuals/ai/sandboxes-api/cookbook/handle-errors-and-degradation.md new file mode 100644 index 0000000000..551c20e4ca --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/handle-errors-and-degradation.md @@ -0,0 +1,106 @@ +--- +title: "Handle errors and degradation" +linkTitle: "Handle errors and degradation" +description: "Tell an API refusal, a failed sandbox transition, and a nonzero command exit apart, and read the code and details each one carries." +keywords: "cloud sandboxes, sandboxes api, handle errors and degradation" +weight: 602 +params: + sidebar: + group: "Requests and responses" +--- + +Distinguish a rejected request from an accepted operation that later fails. This keeps retries from hiding invalid input or duplicating work. + +The SDK exposes typed request and wait errors. A command's nonzero exit code is a third outcome: the SDK can successfully observe a command that failed. + +## Classify the failure {#1-classify-the-failure} + +Use the error type and structured code, not the text of its message. The example groups common request failures and retains their details for the caller. + +- Invalid arguments need a corrected request. +- Authentication failures need a valid credential; permission failures need appropriate account access. +- A wrong-state error needs a state check before another attempt. +- A capacity or quota refusal may require waiting or reducing usage. +- A transient availability failure may be retryable if the operation can be repeated safely. + +Wait errors retain the state or resource observed before the wait stopped. Inspect that information and read the resource again when necessary. A failed create, an interrupted wait, and an expired client deadline do not have the same cleanup outcome. + +A process helper can fail while obtaining a sandbox credential or reading output after the process has started. The error retains the accepted process and its cause. That process may still be running: inspect it before deciding whether to retry the command. Inspect the cause chain for a typed credential-service rate-limit error, including any request identifier and retry delay supplied by the service. + +Unknown errors remain unknown in the example. They may be connection failures before any response arrived. Do not assume that a write was never accepted. + +Keep credentials and secret-bearing request bodies out of diagnostics. Record resource names, error codes, and request identifiers instead. See [safe retries](retry-without-creating-duplicates.md) before repeating a mutation. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +if (!(error instanceof RequestError)) { + return { kind: 'other', code: undefined, details: [] }; +} +const { code, details } = error.raw; +const known = KINDS.get(code); +return { kind: known ?? 'other', code, details: details ?? [] }; +``` + +
+Complete TypeScript example: failures/classify.ts + +```typescript +import { RequestError, ResourceWaitError } from '@docker/sandboxes'; + +type Status = RequestError['raw']; +type Detail = NonNullable[number]; + +export type FailureKind = + | 'invalid-request' + | 'not-served' + | 'wrong-state' + | 'exhausted' + | 'access-or-missing' + | 'unavailable' + | 'other'; + +export interface Failure { + kind: FailureKind; + code: string | undefined; + details: Detail[]; +} + +const KINDS = new Map([ + ['invalidArgument', 'invalid-request'], + ['unimplemented', 'not-served'], + ['failedPrecondition', 'wrong-state'], + ['resourceExhausted', 'exhausted'], + ['unauthenticated', 'access-or-missing'], + ['permissionDenied', 'access-or-missing'], + ['notFound', 'access-or-missing'], + ['unavailable', 'unavailable'], +]); + +export function readFailure(error: unknown): Failure { + if (!(error instanceof RequestError)) { + return { kind: 'other', code: undefined, details: [] }; + } + const { code, details } = error.raw; + const known = KINDS.get(code); + return { kind: known ?? 'other', code, details: details ?? [] }; +} + +export function readSandboxWait( + error: unknown, +): 'degraded' | 'failed' | 'other' { + if (error instanceof ResourceWaitError) { + if (error.kind === 'interrupted' && error.state === 'degraded') + return 'degraded'; + if (error.kind === 'failure' && error.state === 'failed') + return 'failed'; + } + return 'other'; +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/keep-a-cloud-sandbox-running.md b/content/manuals/ai/sandboxes-api/cookbook/keep-a-cloud-sandbox-running.md new file mode 100644 index 0000000000..3030d11265 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/keep-a-cloud-sandbox-running.md @@ -0,0 +1,114 @@ +--- +title: "Keep a cloud sandbox running" +linkTitle: "Keep a cloud sandbox running" +description: "Choose a sandbox's lifetime and what happens when it ends, then renew the lifetime before the deadline passes." +keywords: "cloud sandboxes, sandboxes api, keep a cloud sandbox running" +weight: 503 +params: + sidebar: + group: "Connect and configure" +--- + +Set a sandbox lifetime to prevent abandoned work from running indefinitely, and choose what happens when it expires. + +You need an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and a managed image. The same lifecycle options can be supplied when launching a kit. + +## Set the lifetime at creation {#1-set-the-lifetime-at-creation} + +Pass the lifetime in the units shown by your SDK and choose the expiry action. The example accepts seconds and converts to the SDK's duration representation. + +Stopping preserves the sandbox for later use; deletion removes it. Check that your choice matches whether the application needs the sandbox's files after expiry. Account limits may restrict permitted durations and actions. + +Accounts with always-on access can instead select the restart action. This preserves memory through automatic stop-and-resume cycles. It retains a concurrency reservation while stopped and is different from restarting in response to an incoming request. + +The example waits for startup. Its wait deadline is separate from the sandbox lifetime: ending a client wait does not delete the sandbox. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const sandbox = await client.create( + { + displayName: name, + image, + lifecycle: { + timeoutMs: lifeSeconds * 1_000, + onTimeout: stopOnTimeout ? 'stop' : 'delete', + }, + }, + { timeoutMs: 300_000, idempotencyKey: requestId }, +); +return sandbox.waitUntilRunning(); +``` + +
+Complete TypeScript example: timeout/deadline.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function createWithDeadline( + client: Sandboxes, + name: string, + image: string, + lifeSeconds: number, + stopOnTimeout: boolean, + requestId: string, +) { + const sandbox = await client.create( + { + displayName: name, + image, + lifecycle: { + timeoutMs: lifeSeconds * 1_000, + onTimeout: stopOnTimeout ? 'stop' : 'delete', + }, + }, + { timeoutMs: 300_000, idempotencyKey: requestId }, + ); + return sandbox.waitUntilRunning(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Extend the remaining lifetime {#2-extend-the-remaining-lifetime} + +Use `renewTimeout()` to extend the sandbox's remaining lifetime. Pass the desired duration from the time the service accepts the renewal, not an increment to add to the existing deadline. Renewal can only extend the lifetime; it cannot shorten it. + +Renew a configured timeout before it expires. If the call fails, inspect the sandbox's current state instead of assuming that renewal took effect. + +The service manages timeouts for automatically restarting sandboxes; do not renew those yourself. + +For request-triggered restart after a stop, see [Resume on demand](let-a-stopped-sandbox-resume-on-demand.md). + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.renewTimeout({ timeoutMs: remainingSeconds * 1_000 }); +``` + +
+Complete TypeScript example: timeout/renew.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function renewSandboxTimeout( + client: Sandboxes, + name: string, + remainingSeconds: number, +) { + const sandbox = await client.get(name); + return sandbox.renewTimeout({ timeoutMs: remainingSeconds * 1_000 }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/let-a-stopped-sandbox-resume-on-demand.md b/content/manuals/ai/sandboxes-api/cookbook/let-a-stopped-sandbox-resume-on-demand.md new file mode 100644 index 0000000000..df6c391c2d --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/let-a-stopped-sandbox-resume-on-demand.md @@ -0,0 +1,94 @@ +--- +title: "Let a stopped sandbox resume on demand" +linkTitle: "Let a stopped sandbox resume on demand" +description: "Choose at creation whether a stopped cloud sandbox starts itself when a request arrives at one of its published ports, then read back the setting in force." +keywords: "cloud sandboxes, sandboxes api, let a stopped sandbox resume on demand" +weight: 508 +params: + sidebar: + group: "Connect and configure" +--- + +Let a request to a published application start a stopped sandbox. This can suit an application that should be available on demand without remaining active between uses. + +Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and a managed image that starts the application. [Publish its port](expose-a-port-from-a-cloud-sandbox.md) before relying on requests to reach it. + +## Configure resume on creation {#1-configure-resume-on-creation} + +Set automatic resume in the sandbox's lifecycle options. Omit the value when you want the service default; explicitly set false to disable it. + +The example waits for the initial startup. Automatic resume does not recover a deleted sandbox, and it does not replace application startup configuration. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const sandbox = await client.create( + { displayName: name, image, lifecycle: { autoResume } }, + { timeoutMs: 300_000, idempotencyKey: requestId }, +); +return sandbox.waitUntilRunning(); +``` + +
+Complete TypeScript example: wake/configure.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function createWithAutoResume( + client: Sandboxes, + name: string, + image: string, + autoResume: boolean | undefined, + requestId: string, +) { + const sandbox = await client.create( + { displayName: name, image, lifecycle: { autoResume } }, + { timeoutMs: 300_000, idempotencyKey: requestId }, + ); + return sandbox.waitUntilRunning(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Read the effective setting {#2-read-the-effective-setting} + +Get the sandbox and inspect the effective automatic-resume value together with its state. This checks what the service recorded; it does not send a request to the application or prove that the application can start. + +To verify the whole flow, stop the sandbox, request its published application URL, and confirm that the application becomes ready. Allow for startup time and keep the application's own authentication enabled. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const sandbox = await client.get(name); +return { + autoResume: sandbox.effectiveFeatures?.timeouts?.autoResume ?? false, + status: sandbox.status, +}; +``` + +
+Complete TypeScript example: wake/verify.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function autoResumeInForce(client: Sandboxes, name: string) { + const sandbox = await client.get(name); + return { + autoResume: sandbox.effectiveFeatures?.timeouts?.autoResume ?? false, + status: sandbox.status, + }; +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/manage-cloud-secrets.md b/content/manuals/ai/sandboxes-api/cookbook/manage-cloud-secrets.md new file mode 100644 index 0000000000..f404fb1c8b --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/manage-cloud-secrets.md @@ -0,0 +1,153 @@ +--- +title: "Manage cloud secrets" +linkTitle: "Manage cloud secrets" +description: "Store a token as a stored secret, list your secrets' metadata, replace the token, and delete the secret." +keywords: "cloud sandboxes, sandboxes api, manage cloud secrets" +weight: 402 +params: + sidebar: + group: "Security and policy" +--- + +Store workload credentials separately from application code. Secret reads return metadata only, so keep the original credential in your secret manager if you need it elsewhere. + +Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md). These examples store a token for a named service. For instructions on giving a kit access to that token, see [Get a stored secret into a sandbox](get-a-stored-secret-into-a-sandbox.md). + +## Create a secret {#1-create-a-secret} + +Pass a display name, service type, token value, and idempotency key. Save the returned resource name for attachments and future updates. Do not log the request or token. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.secrets.create( + { displayName: name, serviceType, token: { value: token } }, + { idempotencyKey: requestId }, +); +``` + +
+Complete TypeScript example: secrets/create.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function createSecret( + client: Sandboxes, + name: string, + serviceType: string, + token: string, + requestId: string, +) { + return client.secrets.create( + { displayName: name, serviceType, token: { value: token } }, + { idempotencyKey: requestId }, + ); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## List stored credentials {#2-list-stored-credentials} + +List secret metadata to find a credential by name or label. The example follows each page. Listing does not reveal secret values. + +Use the resource name, not the display name, when attaching a secret to a sandbox. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.secrets.all().collect(); +``` + +
+Complete TypeScript example: secrets/list.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function listSecrets(client: Sandboxes) { + return client.secrets.all().collect(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Rotate the credential {#3-rotate-the-credential} + +Read the existing secret, then update it through a handle with the replacement value and service type. The handle carries the version it read so an intervening change is detected. + +The update replaces the record's writable content. Send the service type again instead of assuming omitted values are preserved. Keep the returned metadata for subsequent operations. + +If another writer changed the secret, read it again and decide whether to apply your replacement. Do not silently overwrite another rotation. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return secret.update( + { serviceType, token: { value: token } }, + { idempotencyKey: requestId }, +); +``` + +
+Complete TypeScript example: secrets/update.ts + +```typescript +import type { Secret } from '@docker/sandboxes'; + +export async function updateSecret( + secret: Secret, + serviceType: string, + token: string, + requestId: string, +) { + return secret.update( + { serviceType, token: { value: token } }, + { idempotencyKey: requestId }, + ); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Delete a secret {#4-delete-a-secret} + +Delete through the secret handle when no workload needs the credential. Deleting an already absent secret is safe for repeated cleanup. + +Removing a stored secret is not a substitute for revoking a leaked credential with its provider. Revoke compromised credentials and replace them before starting new workloads. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +await secret.delete(); +``` + +
+Complete TypeScript example: secrets/delete.ts + +```typescript +import type { Secret } from '@docker/sandboxes'; + +export async function deleteSecret(secret: Secret) { + await secret.delete(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/manage-network-policies.md b/content/manuals/ai/sandboxes-api/cookbook/manage-network-policies.md new file mode 100644 index 0000000000..a0f1e1fd78 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/manage-network-policies.md @@ -0,0 +1,75 @@ +--- +title: "Manage network policies" +linkTitle: "Manage network policies" +description: "Create, read, update, and delete personal network policies through the SDK." +keywords: "cloud sandboxes, sandboxes api, manage network policies" +weight: 406 +params: + sidebar: + group: "Security and policy" +--- + +Create reusable network policies in your Docker account, then attach their IDs when creating sandboxes. The SDK's governance collection uses the same Docker authentication as the sandbox client. + +Start with an [authenticated client](connect-to-cloud-with-a-bearer-token.md). These methods manage your personal policies, not organization-wide administration. + +## Manage a policy's lifetime {#1-manage-a-policy-s-lifetime} + +The example creates a policy, lists the available policies, reads the new one, and replaces its definition. It attempts to delete the temporary policy in cleanup, including when a later step fails. Check cleanup errors; a cancelled request can leave the policy behind. + +Supply a policy definition and a replacement using your SDK's policy input type. Include the destinations your workload needs. For a deny-by-default policy, allow the agent's model provider and any required package registries explicitly. + +Set `status: 'POLICY_STATUS_ACTIVE'` on both the policy and its replacement. Although the SDK type makes this field optional, the service rejects omitted or other values with `unimplemented`. + +An update replaces the definition rather than appending rules. Read the existing policy before deciding what to retain. Personal-policy mutations are not automatically replayed, so inspect the current state after an uncertain result before trying again. + +This is a disposable policy-management example: the returned policy ID has been deleted by the time the function finishes. To keep a policy for real workloads, remove that demonstration cleanup and store its ID with your application configuration. Follow [Control what a sandbox can reach](control-what-a-sandbox-can-reach.md) to attach it and inspect enforced rules. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const created = await client.governance.policies.create(policy); +try { + const listed = await client.governance.policies.list(); + const current = await client.governance.policies.get(created.id); + const updated = await client.governance.policies.update( + created.id, + replacement, + ); + return { listed, current, updated }; +} finally { + await client.governance.policies.delete(created.id); +} +``` + +
+Complete TypeScript example: policies/manage.ts + +```typescript +import type { GovernancePolicyInput, Sandboxes } from '@docker/sandboxes'; + +export async function managePolicy( + client: Sandboxes, + policy: GovernancePolicyInput, + replacement: GovernancePolicyInput, +) { + const created = await client.governance.policies.create(policy); + try { + const listed = await client.governance.policies.list(); + const current = await client.governance.policies.get(created.id); + const updated = await client.governance.policies.update( + created.id, + replacement, + ); + return { listed, current, updated }; + } finally { + await client.governance.policies.delete(created.id); + } +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/page-through-and-filter-lists.md b/content/manuals/ai/sandboxes-api/cookbook/page-through-and-filter-lists.md new file mode 100644 index 0000000000..3b4eed4599 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/page-through-and-filter-lists.md @@ -0,0 +1,76 @@ +--- +title: "Page through and filter lists" +linkTitle: "Page through and filter lists" +description: "Walk every page of a list with the SDK's iterators, and narrow a list on the server with a filter and an ordering." +keywords: "cloud sandboxes, sandboxes api, page through and filter lists" +weight: 604 +params: + sidebar: + group: "Requests and responses" +--- + +List resources without losing results at page boundaries. SDK collection iterators request each page for you; collecting materializes the complete result in memory. + +Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md). For a large account, process iterator items as they arrive instead of collecting them all. + +## Walk every sandbox {#1-walk-every-sandbox} + +Set a page size and iterate the sandbox collection. The example collects the results for convenience. A page size controls each request, not the total number of returned resources. + +When using one-page methods directly, pass the returned next-page token unchanged and keep the filter, order, and page size stable. Stop when no next token is returned, not when a page contains fewer items than requested. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.all({ pageSize }).collect(); +``` + +
+Complete TypeScript example: pagination/walk.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function walkSandboxes(client: Sandboxes, pageSize: number) { + return client.all({ pageSize }).collect(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Filter and order the result {#2-filter-and-order-the-result} + +Pass a filter and ordering supported by the collection. The example uses images; choose a filter such as `status=completed` to select ready images. + +Filters are strings interpreted by the service. Use the field names and operators documented for that list method rather than a language object's property names. An invalid filter should be fixed, not silently removed and retried as an unfiltered list. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.images.all({ filter, orderBy }).collect(); +``` + +
+Complete TypeScript example: pagination/filter.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function filterImages( + client: Sandboxes, + filter: string, + orderBy: string, +) { + return client.images.all({ filter, orderBy }).collect(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/read-files-out-of-a-sandbox.md b/content/manuals/ai/sandboxes-api/cookbook/read-files-out-of-a-sandbox.md new file mode 100644 index 0000000000..6ae6a23ea2 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/read-files-out-of-a-sandbox.md @@ -0,0 +1,212 @@ +--- +title: "Read files out of a sandbox" +linkTitle: "Read files out of a sandbox" +description: "List a directory in a running cloud sandbox, download files from it into memory, then move or remove paths inside it." +keywords: "cloud sandboxes, sandboxes api, read files out of a sandbox" +weight: 203 +params: + sidebar: + group: "Working in a sandbox" +--- + +Read build results, inspect project files, or copy data out before deleting a sandbox. Start with a running sandbox handle and an absolute path inside it. + +The examples return content to your application. To keep it on your machine, write the received bytes to a local file. + +## List a directory {#1-list-a-directory} + +Walk the directory through the file collection's iterator. It follows pagination and returns the entries. Listing gives metadata, not file content. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.files.all(path).collect(); +``` + +
+Complete TypeScript example: download/list.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function listDirectory(sandbox: Sandbox, path: string) { + return sandbox.files.all(path).collect(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Read a small text file {#2-read-a-small-text-file} + +Use the bounded read helper for text you want in memory. The example limits the read to one MiB. Choose a bound that fits your application's memory budget, or download a larger file as a stream. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.files.read(path, { + encoding: 'utf8', + maxBytes: 1024 * 1024, +}); +``` + +
+Complete TypeScript example: download/read.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function readFile(sandbox: Sandbox, path: string) { + return sandbox.files.read(path, { + encoding: 'utf8', + maxBytes: 1024 * 1024, + }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Inspect a path {#3-inspect-a-path} + +Read metadata before deciding whether to download, move, or remove a path. A successful metadata read does not reserve the file: another process may change it afterward. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.files.stat(path); +``` + +
+Complete TypeScript example: download/stat.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function statFile(sandbox: Sandbox, path: string) { + return sandbox.files.stat(path); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Download a file as a stream {#4-download-a-file-as-a-stream} + +Pass the sandbox path and a callback or writer that consumes bytes. The example closes the transfer when it finishes or fails. + +Treat the download as complete only when it ends successfully. If it fails halfway through, discard or separately identify the partial local file before retrying. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const download = await sandbox.files.download(path); +try { + for await (const chunk of download) write(chunk); +} finally { + await download.close(); +} +``` + +
+Complete TypeScript example: download/download.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function downloadFiles( + sandbox: Sandbox, + path: string, + write: (bytes: Uint8Array) => void, +) { + const download = await sandbox.files.download(path); + try { + for await (const chunk of download) write(chunk); + } finally { + await download.close(); + } +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Move a path {#5-move-a-path} + +Pass the current path and destination. This moves data inside the sandbox; it does not download anything to your computer. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +await sandbox.files.move(from, to); +``` + +
+Complete TypeScript example: download/move.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function movePath( + sandbox: Sandbox, + from: string, + to: string, +) { + await sandbox.files.move(from, to); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Remove a path {#6-remove-a-path} + +Remove a file, or enable recursive removal for a directory tree. Check the result for a failed path rather than assuming that every requested removal succeeded. + +Recursive removal is destructive. Keep user-supplied paths constrained to the directory your application owns. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const result = await sandbox.files.remove(path, { recursive }); +if (result.failedPath) + throw new Error(`Remove ${path} stopped at ${result.failedPath}`); +``` + +
+Complete TypeScript example: download/remove.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function removePath( + sandbox: Sandbox, + path: string, + recursive: boolean, +) { + const result = await sandbox.files.remove(path, { recursive }); + if (result.failedPath) + throw new Error(`Remove ${path} stopped at ${result.failedPath}`); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/recover-when-the-endpoint-moves.md b/content/manuals/ai/sandboxes-api/cookbook/recover-when-the-endpoint-moves.md new file mode 100644 index 0000000000..d5728c2bf1 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/recover-when-the-endpoint-moves.md @@ -0,0 +1,88 @@ +--- +title: "Recover when the endpoint moves" +linkTitle: "Recover when the endpoint moves" +description: "Read a sandbox again to learn its current endpoint, then run one command through a connection built for that endpoint." +keywords: "cloud sandboxes, sandboxes api, recover when the endpoint moves" +weight: 704 +params: + sidebar: + group: "Long-running work" +--- + +Refresh a sandbox handle after a restart or connection change. A saved address can become stale even though the sandbox still has the same resource name. + +Start with the handle your application already holds. Keep its name and UID; do not replace them with another sandbox's identity. + +## Compare the current endpoint {#1-compare-the-current-endpoint} + +Refresh the handle, then compare the old and new connection information. The SDK checks the resource identity while constructing the refreshed handle. + +The example reports whether the endpoint changed and returns a refreshed handle. Use that handle for later process and file calls. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const current = await held.refresh(); +const before = held.core.endpoint; +const after = current.core.endpoint; +return { + moved: + before?.uri !== after?.uri || before?.protocol !== after?.protocol, + current, +}; +``` + +
+Complete TypeScript example: rebind/detect.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function endpointMoved(held: Sandbox) { + const current = await held.refresh(); + const before = held.core.endpoint; + const after = current.core.endpoint; + return { + moved: + before?.uri !== after?.uri || before?.protocol !== after?.protocol, + current, + }; +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Run through the refreshed handle {#2-run-through-the-refreshed-handle} + +Refresh before opening a new process connection. The SDK obtains the appropriate sandbox credential for the current endpoint; your application does not need to pass its Docker account token there. + +This example starts new work after refresh. It does not replay a command whose completion is unknown. If an earlier command may still be running, [find and reconnect to it](find-a-process-you-lost-track-of.md) first. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const current = await held.refresh(); +return current.processes.run({ args }, { timeoutMs: 300_000 }); +``` + +
+Complete TypeScript example: rebind/rebuild.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function runAndRebind(held: Sandbox, args: string[]) { + const current = await held.refresh(); + return current.processes.run({ args }, { timeoutMs: 300_000 }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/register-and-manage-an-image.md b/content/manuals/ai/sandboxes-api/cookbook/register-and-manage-an-image.md new file mode 100644 index 0000000000..a74f1aad8d --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/register-and-manage-an-image.md @@ -0,0 +1,148 @@ +--- +title: "Register and manage an image" +linkTitle: "Register and manage an image" +description: "Register an image with Cloud Sandboxes, get the target to push its content to, check when it is ready, list your images, and delete one you no longer need." +keywords: "cloud sandboxes, sandboxes api, register and manage an image" +weight: 301 +params: + sidebar: + group: "Packaging and state" +--- + +Reuse an image across sandboxes by registering it with Docker Cloud Sandboxes. Registration creates an image record and a temporary upload destination. You must push the image content separately with an OCI-compatible registry client. + +Start with an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and a built image. If you already have an image in a registry, [run it directly](run-your-own-container-image.md) instead. + +## Register an upload destination {#1-register-an-upload-destination} + +Choose a display name, startup command, CPU and memory preferences, and an idempotency key. The returned image includes its resource name and a push target. + +Use the target's registry reference and temporary credential to push your image. Keep that credential private and finish before it expires. Save the target from the initial response: later image reads do not issue a replacement upload credential. + +This example registers the destination. It does not build or push image content. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.images.create( + { displayName: name, fromImage: { resources }, startCmd }, + { idempotencyKey: requestId }, +); +``` + +
+Complete TypeScript example: images/create.ts + +```typescript +import type { + ClientImagesCreateOptions, + Sandboxes, +} from '@docker/sandboxes'; + +export async function createImage( + client: Sandboxes, + name: string, + startCmd: string[], + resources: Extract< + ClientImagesCreateOptions, + { fromImage: unknown } + >['fromImage']['resources'], + requestId: string, +) { + return client.images.create( + { displayName: name, fromImage: { resources }, startCmd }, + { idempotencyKey: requestId }, + ); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Check preparation status {#2-check-preparation-status} + +After the push finishes, read the image by its resource name. A `COMPLETED` status means it is ready to use. `WAITING_FOR_PUSH` means content has not arrived, `PREPARING` means preparation is in progress, and `FAILED` includes failure details. + +The example performs one read. If preparation is still in progress, repeat the read with a delay and deadline. Repeating creation would register another image rather than advance this one. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.images.get(name); +``` + +
+Complete TypeScript example: images/ready.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function getImage(client: Sandboxes, name: string) { + return client.images.get(name); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Find registered images {#3-find-registered-images} + +List images with an optional filter, such as `status=completed`. The example follows all pages. List entries are summaries; get the image by name when you need its complete record. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.images.all({ filter }).collect(); +``` + +
+Complete TypeScript example: images/list.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function listImages(client: Sandboxes, filter: string) { + return client.images.all({ filter }).collect(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Remove an image {#4-remove-an-image} + +Read the image and delete through its handle. The SDK supplies its name and version to protect against deleting a newer record you have not read. + +Keep images that future sandbox creations still depend on. Deleting an already absent image is safe for cleanup. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +await image.delete(); +``` + +
+Complete TypeScript example: images/delete.ts + +```typescript +import type { Image } from '@docker/sandboxes'; + +export async function deleteImage(image: Image) { + await image.delete(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/retry-without-creating-duplicates.md b/content/manuals/ai/sandboxes-api/cookbook/retry-without-creating-duplicates.md new file mode 100644 index 0000000000..20af1095bb --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/retry-without-creating-duplicates.md @@ -0,0 +1,127 @@ +--- +title: "Retry without creating duplicates" +linkTitle: "Retry without creating duplicates" +description: "Retry a sandbox or process create under one request ID so a failure you can't interpret never leaves two copies behind." +keywords: "cloud sandboxes, sandboxes api, retry without creating duplicates" +weight: 603 +params: + sidebar: + group: "Requests and responses" +--- + +Retry a request without creating duplicate work. An idempotency key identifies one logical operation, such as creating the sandbox for a particular job. + +Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md). Generate the key once, then persist it with the job if retries can happen in another program run. + +## Retry the same create request {#1-retry-the-same-create-request} + +Reuse the same key and payload on every attempt. The example retries a transient availability refusal and disables automatic SDK retries. Do not run your own retry loop and the SDK's together without accounting for their total attempts. + +Use a deadline and a bounded attempt count. For production retry scheduling, add delay and jitter or use the SDK's configured retry behavior. Changing the payload while keeping the key is not an update; it conflicts with the original request. + +An accepted creation returns the sandbox's resource data. Save its name and use the client's get method to obtain a handle. Read or wait on that resource to follow progress instead of repeating creation to make it advance. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +for (let attempt = 0; attempt < attempts; attempt++) { + try { + return await client.kits.launch( + 'shell', + { displayName, resources: { cpus: 2, memoryMib: 4096 } }, + { + idempotencyKey: requestId, + timeoutMs: 300_000, + signal, + maxRetries: 0, + }, + ); + } catch (error) { + if ( + !(error instanceof RequestError) || + error.raw.code !== 'unavailable' || + attempt + 1 === attempts + ) + throw error; + } +} +``` + +
+Complete TypeScript example: idempotency/retry.ts + +```typescript +import { RequestError, type Sandboxes } from '@docker/sandboxes'; + +export async function createWithRetry( + client: Sandboxes, + displayName: string, + requestId: string, + attempts: number, +) { + if (!Number.isInteger(attempts) || attempts < 1) + throw new RangeError('attempts must be at least 1'); + const signal = AbortSignal.timeout(300_000); + for (let attempt = 0; attempt < attempts; attempt++) { + try { + return await client.kits.launch( + 'shell', + { displayName, resources: { cpus: 2, memoryMib: 4096 } }, + { + idempotencyKey: requestId, + timeoutMs: 300_000, + signal, + maxRetries: 0, + }, + ); + } catch (error) { + if ( + !(error instanceof RequestError) || + error.raw.code !== 'unavailable' || + attempt + 1 === attempts + ) + throw error; + } + } + throw new Error('attempt count was validated'); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Start a process once {#2-start-a-process-once} + +Start the process with its own idempotency key and keep the returned process name. After losing a response or connection, find that process before starting another one. + +Keys do not make every operation safe to replay. Repeating process input, a signal, or a file write can have a second effect. Use [process reconnection](find-a-process-you-lost-track-of.md) for a lost stream and inspect files before repeating an uncertain write. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.processes.start({ args }, { idempotencyKey: requestId }); +``` + +
+Complete TypeScript example: idempotency/unsafe.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function createProcessOnce( + sandbox: Sandbox, + args: string[], + requestId: string, +) { + return sandbox.processes.start({ args }, { idempotencyKey: requestId }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/run-a-complete-example.md b/content/manuals/ai/sandboxes-api/cookbook/run-a-complete-example.md new file mode 100644 index 0000000000..e13ffd8431 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/run-a-complete-example.md @@ -0,0 +1,101 @@ +--- +title: "Run a complete example" +linkTitle: "Run a complete example" +description: "Sign in, launch the shell kit, print a greeting, and clean up with one runnable program." +keywords: "cloud sandboxes, sandboxes api, run a complete example" +weight: 102 +params: + sidebar: + group: "Get started" +--- + +Run a small program that signs in to Docker, creates a sandbox from the `shell` kit, prints a greeting, and deletes the sandbox. You do not need a model-provider key for this example. + +[Install your SDK](https://docs.docker.com/ai/sandboxes-api/install/) first. Your Docker account must have Cloud Sandboxes access and billing set up. This program creates a real sandbox with 2 CPUs and 4096 MiB of memory; compute usage is subject to your Docker billing terms. + +## Run the program {#1-run-the-program} + +Expand the complete example and copy the whole program into a file. Save it as `example.mts`, then run `npx tsx example.mts`. + +The program prints a verification URL and code when its first API request needs authentication. Open the URL, enter the code, and approve sign-in. Your terminal then shows the sandbox's name and `Hello from Docker Sandboxes`. The program checks the command's exit status and cleans up its sandbox before closing the client. + +If authentication fails, check the steps in [Authenticate to Docker](connect-to-cloud-with-a-bearer-token.md). If creation is refused, check account access and [resource limits](work-within-the-limits.md). A timeout does not prove that an accepted sandbox was deleted; keep any sandbox name reported with a cleanup error so you can inspect it. + +This example deletes its sandbox because it is a one-off demonstration. Next, [keep a sandbox for later work](create-your-first-sandbox.md) or [run an agent kit](add-tools-with-kits.md). + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const result = await sandbox.processes.run( + { + args: ['echo', 'Hello from Docker Sandboxes'], + }, + operation, +); +console.log(result.stdout); +``` + +
+Complete TypeScript example: hello/main.ts + +```typescript +import { pathToFileURL } from 'node:url'; +import { oauth, Sandboxes, type Sandbox } from '@docker/sandboxes'; + +export async function main() { + const auth = oauth({ + onVerification: ({ verificationUri, userCode }) => { + console.log(`Open ${verificationUri} and enter ${userCode}`); + }, + }); + const client = new Sandboxes({ auth }); + let sandbox: Sandbox | undefined; + try { + const operation = { timeoutMs: 300_000 }; + sandbox = await client.kits.launch( + 'shell', + { + resources: { cpus: 2, memoryMib: 4096 }, + }, + operation, + ); + console.log(`Sandbox: ${sandbox.name}`); + sandbox = await sandbox.waitUntilRunning(operation); + const result = await sandbox.processes.run( + { + args: ['echo', 'Hello from Docker Sandboxes'], + }, + operation, + ); + console.log(result.stdout); + if (result.exitCode !== 0) + throw new Error(`Command exited with status ${result.exitCode}`); + } finally { + try { + if (sandbox) { + const cleanup = { signal: AbortSignal.timeout(30_000) }; + const deleting = await sandbox.delete({ force: true }, cleanup); + await deleting?.waitUntilDeleted(cleanup); + } + } finally { + await client.close(); + } + } +} + +if ( + process.argv[1] && + import.meta.url === pathToFileURL(process.argv[1]).href +) { + main().catch((error: unknown) => { + console.error(error); + process.exitCode = 1; + }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/run-an-interactive-shell-in-a-cloud-sandbox.md b/content/manuals/ai/sandboxes-api/cookbook/run-an-interactive-shell-in-a-cloud-sandbox.md new file mode 100644 index 0000000000..e8dd32c775 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/run-an-interactive-shell-in-a-cloud-sandbox.md @@ -0,0 +1,220 @@ +--- +title: "Run an interactive shell in a cloud sandbox" +linkTitle: "Run an interactive shell in a cloud sandbox" +description: "Start a shell on a pseudo-terminal, send it input and read its output over one connection, connect again and pick up the output the service kept, and stop it." +keywords: "cloud sandboxes, sandboxes api, run an interactive shell in a cloud sandbox" +weight: 201 +params: + sidebar: + group: "Working in a sandbox" +--- + +Run a process with a terminal when it expects interactive input. A terminal session differs from a captured command: you manage input, output, and connection lifetime. + +Use a running sandbox handle from [your first sandbox](create-your-first-sandbox.md). Pass the command as an argument array, such as `['sh']`. + +## Start a terminal process {#1-start-a-terminal-process} + +Start the process with a pseudo-terminal and an initial terminal size. The returned process handle identifies this session. Save its resource name if you want to reconnect later. + +Use one idempotency key for this start request. Reconnecting does not require starting another process. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.processes.start( + { args, pty: { initialSize: { rows: 40, cols: 120 } } }, + { idempotencyKey: requestId }, +); +``` + +
+Complete TypeScript example: shell/create.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function createProcess( + sandbox: Sandbox, + args: string[], + requestId: string, +) { + return sandbox.processes.start( + { args, pty: { initialSize: { rows: 40, cols: 120 } } }, + { idempotencyKey: requestId }, + ); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Send input and read output {#2-send-input-and-read-output} + +Connect to the process, write input, and consume output events. This example sends a finite input buffer and closes standard input; a terminal application should keep input open until the user finishes. + +Forward output chunks to your terminal or callback. An exit event reports the process exit code. A connection ending without that event does not establish that the command finished. + +Close the connection when you stop consuming it. Closing a connection and terminating the process are separate actions. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const connection = await process.connect(); +try { + await connection.write(stdin); + await connection.closeStdin(); + for await (const event of connection) { + if (event.type === 'chunk') + write( + event.data ?? new Uint8Array(), + BigInt(event.streamSequence ?? '0'), + ); + if (event.type === 'exited') return event.exitCode ?? 0; + } + throw new Error( + `Stream for ${process.name} ended before the process exited`, + ); +} finally { + await connection.close(); +} +``` + +
+Complete TypeScript example: shell/attach.ts + +```typescript +import type { Process } from '@docker/sandboxes'; + +export async function attachToProcess( + process: Process, + stdin: Uint8Array, + write: (bytes: Uint8Array, sequence: bigint) => void, +) { + const connection = await process.connect(); + try { + await connection.write(stdin); + await connection.closeStdin(); + for await (const event of connection) { + if (event.type === 'chunk') + write( + event.data ?? new Uint8Array(), + BigInt(event.streamSequence ?? '0'), + ); + if (event.type === 'exited') return event.exitCode ?? 0; + } + throw new Error( + `Stream for ${process.name} ended before the process exited`, + ); + } finally { + await connection.close(); + } +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Resume output after a disconnect {#3-resume-output-after-a-disconnect} + +Record the sequence number after handling each output chunk. On reconnect, pass the last handled sequence number to resume after that point. + +Connections opened through TypeScript process handles resume automatically after temporary disconnects while you consume output. They use the last chunk delivered to your application, not the last chunk your application saved elsewhere. Raw streams require explicit reconnection. Keep your own cursor if you need to resume after an application restart. Input is never replayed; if a write fails, check the process before sending that input again. + +Retain the process name with the cursor. A cursor from one process cannot identify output from another. Persist the cursor only after your application has handled the corresponding output. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const connection = await process.connect({ resumeFrom }); +let lastSequence = BigInt(resumeFrom); +try { + for await (const event of connection) { + if (event.type === 'chunk') { + write( + event.data ?? new Uint8Array(), + (lastSequence = BigInt(event.streamSequence ?? '0')), + ); + } + } + return lastSequence; +} finally { + await connection.close(); +} +``` + +
+Complete TypeScript example: shell/resume.ts + +```typescript +import type { Process } from '@docker/sandboxes'; + +export async function resumeProcessOutput( + process: Process, + resumeFrom: bigint, + write: (bytes: Uint8Array, sequence: bigint) => void, +) { + const connection = await process.connect({ resumeFrom }); + let lastSequence = BigInt(resumeFrom); + try { + for await (const event of connection) { + if (event.type === 'chunk') { + write( + event.data ?? new Uint8Array(), + (lastSequence = BigInt(event.streamSequence ?? '0')), + ); + } + } + return lastSequence; + } finally { + await connection.close(); + } +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Send a signal {#4-send-a-signal} + +Get the process by name and send the signal your application intends. Use a graceful termination signal when the process should clean up its own files. + +Signals operate on the process; they do not delete the sandbox. [Delete the sandbox](delete-a-cloud-sandbox.md) separately when all work is finished. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const process = await sandbox.processes.get(name); +await process.signal(signal); +``` + +
+Complete TypeScript example: shell/signal.ts + +```typescript +import type { Sandbox, Process } from '@docker/sandboxes'; + +export async function signalProcess( + sandbox: Sandbox, + name: string, + signal: Parameters[0], +) { + const process = await sandbox.processes.get(name); + await process.signal(signal); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/run-something-that-produces-real-output.md b/content/manuals/ai/sandboxes-api/cookbook/run-something-that-produces-real-output.md new file mode 100644 index 0000000000..a9b105636d --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/run-something-that-produces-real-output.md @@ -0,0 +1,111 @@ +--- +title: "Run something that produces real output" +linkTitle: "Run something that produces real output" +description: "Choose between a call that returns a command's captured output when it finishes and a process whose output you read while it runs." +keywords: "cloud sandboxes, sandboxes api, run something that produces real output" +weight: 210 +params: + sidebar: + group: "Working in a sandbox" +--- + +Choose how your application receives command output. Captured output is convenient for a short command; streaming lets you display progress or process output without waiting for completion. + +Start with a running sandbox handle from [your first sandbox](create-your-first-sandbox.md). Supply the command as an argument array. + +## Collect the result {#1-collect-the-result} + +Run the process and wait for its result. Inspect standard output, standard error, and the exit code. Use an application deadline so a command cannot hold your request open indefinitely. + +This form accumulates output for you. Prefer streaming when output could be large or your user needs progress updates. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.processes.run({ args }, { timeoutMs: 300_000 }); +``` + +
+Complete TypeScript example: longrun/choose.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function collectOutput(sandbox: Sandbox, args: string[]) { + return sandbox.processes.run({ args }, { timeoutMs: 300_000 }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Stream output as it arrives {#2-stream-output-as-it-arrives} + +Start the process, connect to it, and consume output events. The example forwards each output chunk to a callback and returns the exit code from the exit event. + +The callback owns what happens to each chunk: display it, append it to a file, or send it to a client. Do not log sensitive output indiscriminately. + +Always close the connection. A stream that ends before an exit event is an incomplete observation, not proof of success. Keep the process name to [reconnect](find-a-process-you-lost-track-of.md) rather than immediately starting a duplicate command. + +Connections opened through TypeScript process handles reconnect after temporary disconnects while you consume output. They resume after the last delivered chunk and stop if recovery exceeds 30 seconds, without replaying process input. These connections have no default lifetime limit once connected, but a timeout you supply still limits the whole session. Raw streams require explicit reconnection. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const process = await sandbox.processes.start( + { args }, + { idempotencyKey: requestId }, +); +const connection = await process.connect(); +try { + for await (const event of connection) { + if (event.type === 'chunk') write(event.data ?? new Uint8Array()); + if (event.type === 'exited') return event.exitCode ?? 0; + } + throw new Error( + `Output of ${process.name} ended before the command exited`, + ); +} finally { + await connection.close(); +} +``` + +
+Complete TypeScript example: longrun/stream.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function streamOutput( + sandbox: Sandbox, + args: string[], + requestId: string, + write: (bytes: Uint8Array) => void, +) { + const process = await sandbox.processes.start( + { args }, + { idempotencyKey: requestId }, + ); + const connection = await process.connect(); + try { + for await (const event of connection) { + if (event.type === 'chunk') write(event.data ?? new Uint8Array()); + if (event.type === 'exited') return event.exitCode ?? 0; + } + throw new Error( + `Output of ${process.name} ended before the command exited`, + ); + } finally { + await connection.close(); + } +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/run-work-across-many-sandboxes.md b/content/manuals/ai/sandboxes-api/cookbook/run-work-across-many-sandboxes.md new file mode 100644 index 0000000000..8be8dc7c4b --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/run-work-across-many-sandboxes.md @@ -0,0 +1,126 @@ +--- +title: "Run work across many sandboxes" +linkTitle: "Run work across many sandboxes" +description: "Run one command on every running sandbox you hold, a bounded number at a time, and read one answer per sandbox even when some of them fail." +keywords: "cloud sandboxes, sandboxes api, run work across many sandboxes" +weight: 703 +params: + sidebar: + group: "Long-running work" +--- + +Run a command across several existing sandboxes while keeping concurrency bounded. This can collect diagnostics or apply the same task to a group of independent workspaces. + +Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and an argument array. Only run the command in sandboxes your application owns for this task. + +## Select running sandboxes {#1-select-running-sandboxes} + +Walk the sandbox collection and retain running handles. The example selects all running sandboxes visible to the client; narrow that selection to your application's jobs before executing a command with side effects. + +Listing and execution are separate requests. A sandbox can change state between them, so the execution step still needs to handle failures. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const running: Sandbox[] = []; +for await (const sandbox of client.all()) { + if (sandbox.status === 'running') running.push(sandbox); +} +return running; +``` + +
+Complete TypeScript example: fanout/spread.ts + +```typescript +import type { Sandbox, Sandboxes } from '@docker/sandboxes'; + +export async function runningSandboxes(client: Sandboxes) { + const running: Sandbox[] = []; + for await (const sandbox of client.all()) { + if (sandbox.status === 'running') running.push(sandbox); + } + return running; +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Collect individual results {#2-collect-individual-results} + +Choose a concurrency bound and run the command on each selected sandbox. The example keeps each result or failure alongside the sandbox name, so one failed request does not discard every other result. + +Inspect both request failures and process exit codes. A returned process result can contain a nonzero exit code. + +Keep concurrency below your account's limits and your application's memory budget. For large output, stream results instead of collecting them all. The example does not delete the existing sandboxes. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const outcomes: Outcome[] = []; +const size = Number.isFinite(bound) ? Math.max(1, Math.trunc(bound)) : 1; +for (let start = 0; start < sandboxes.length; start += size) { + const group = sandboxes.slice(start, start + size); + const settled = await Promise.allSettled( + group.map((sandbox) => + sandbox.processes.run({ args }, { timeoutMs: 300_000 }), + ), + ); + settled.forEach((result, index) => { + outcomes.push( + result.status === 'fulfilled' + ? { sandboxName: group[index].name, response: result.value } + : { sandboxName: group[index].name, failure: result.reason }, + ); + }); +} +return outcomes; +``` + +
+Complete TypeScript example: fanout/gather.ts + +```typescript +import type { RunResult, Sandbox } from '@docker/sandboxes'; + +export type Outcome = { + sandboxName: string; + response?: RunResult; + failure?: unknown; +}; + +export async function runOnEach( + sandboxes: Sandbox[], + args: string[], + bound: number, +) { + const outcomes: Outcome[] = []; + const size = Number.isFinite(bound) ? Math.max(1, Math.trunc(bound)) : 1; + for (let start = 0; start < sandboxes.length; start += size) { + const group = sandboxes.slice(start, start + size); + const settled = await Promise.allSettled( + group.map((sandbox) => + sandbox.processes.run({ args }, { timeoutMs: 300_000 }), + ), + ); + settled.forEach((result, index) => { + outcomes.push( + result.status === 'fulfilled' + ? { sandboxName: group[index].name, response: result.value } + : { sandboxName: group[index].name, failure: result.reason }, + ); + }); + } + return outcomes; +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/run-your-first-command.md b/content/manuals/ai/sandboxes-api/cookbook/run-your-first-command.md new file mode 100644 index 0000000000..4ea3a617db --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/run-your-first-command.md @@ -0,0 +1,112 @@ +--- +title: "Run your first command" +linkTitle: "Run your first command" +description: "Connect to a running sandbox, run a command in it, and tell a command that exited unsuccessfully apart from a call that failed." +keywords: "cloud sandboxes, sandboxes api, run your first command" +weight: 105 +params: + sidebar: + group: "Get started" +--- + +Run a command in an existing sandbox and collect its output. Start with an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and the sandbox name returned by [your first launch](create-your-first-sandbox.md). + +For most commands, use the process collection's run helper. It starts the process and waits for its result. + +## Open the sandbox {#1-open-the-sandbox} + +Get the sandbox by its resource name. The returned handle exposes processes and files and manages their authenticated connections. You do not need to copy the sandbox address or Docker token into a second client. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.get(name); +``` + +
+Complete TypeScript example: exec/endpoint.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function processesAt(client: Sandboxes, name: string) { + return client.get(name); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Use the process run helper {#2-use-the-process-run-helper} + +Pass an argument array: the program followed by its arguments, such as `['echo', 'Hello from Docker Sandboxes']`. Arguments are not shell syntax. For pipes, redirection, or variable expansion, run `sh -c` with your script as the next argument. + +The run helper returns the process result after collecting output. A successful SDK call does not mean the command succeeded: inspect its exit code. Standard error is diagnostic output and can be nonempty even when the exit code is zero. + +An SDK error means the request, wait, or connection failed. Keep the process handle carried by an error when available so you can inspect it instead of starting duplicate work. [Error handling](handle-errors-and-degradation.md) explains the distinction. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return sandbox.processes.run({ args }, { timeoutMs: 300_000 }); +``` + +
+Complete TypeScript example: exec/outcome.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function classifyOutcome(sandbox: Sandbox, args: string[]) { + return sandbox.processes.run({ args }, { timeoutMs: 300_000 }); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Optional: execute a command directly {#3-optional-execute-a-command-directly} + +For a short command whose output you only need after it exits, direct execution is simpler: one request waits for the command and returns its captured output and exit code, without a separate process-creation and output-reading sequence. + +Check the incomplete flag before treating captured output as complete. Choose [streaming output](run-something-that-produces-real-output.md) when you need output while the command runs. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const result = await endpoint.api.exec({ body: { cmd: args } }); +``` + +
+Complete TypeScript example: exec/exec.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function runCommand(sandbox: Sandbox, args: string[]) { + const endpoint = await sandbox.endpointClient(['sandboxesExec'], 'exec'); + try { + const result = await endpoint.api.exec({ body: { cmd: args } }); + return { + exitCode: result.exitCode ?? 0, + stdout: new TextDecoder().decode(result.stdout), + stderr: new TextDecoder().decode(result.stderr), + incomplete: result.incomplete ?? false, + }; + } finally { + await endpoint.close(); + } +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/run-your-own-container-image.md b/content/manuals/ai/sandboxes-api/cookbook/run-your-own-container-image.md new file mode 100644 index 0000000000..fa3042c63a --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/run-your-own-container-image.md @@ -0,0 +1,61 @@ +--- +title: "Run your own container image" +linkTitle: "Run your own container image" +description: "Start a sandbox from a container image in a registry you name, instead of from a managed image." +keywords: "cloud sandboxes, sandboxes api, run your own container image" +weight: 501 +params: + sidebar: + group: "Connect and configure" +--- + +Run your own tools from a container image when a [bundled kit](add-tools-with-kits.md) does not fit the task. You supply the image reference and machine size; the SDK creates an isolated sandbox around it. + +You need an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and an OCI image the service can pull. Use a versioned reference or digest when repeatability matters. The image must include `/bin/sh`. + +## Create from a registry image {#1-create-from-a-registry-image} + +Pass the image reference, CPU count, memory size, display name, and idempotency key. The SDK's image-reference option avoids assembling nested request fields. Do not also supply a managed image or named agent. + +The example waits for the sandbox to run. A wait timeout stops your wait; it does not delete a sandbox that was already accepted. Save any sandbox handle retained by the error so you can inspect or clean it up. + +The image's startup command runs inside the sandbox. Read `WORKSPACE_DIR` to find the workspace rather than assuming a path. + +Next, [run a command](run-your-first-command.md) or [copy in your project files](copy-a-file-into-a-cloud-sandbox.md). [Delete the sandbox](delete-a-cloud-sandbox.md) when the work is complete. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const sandbox = await client.create( + { displayName: name, imageRef, resources }, + { timeoutMs: 300_000, idempotencyKey: requestId }, +); +return sandbox.waitUntilRunning(); +``` + +
+Complete TypeScript example: rawimage/create.ts + +```typescript +import type { ClientCreateOptions, Sandboxes } from '@docker/sandboxes'; + +export async function createFromImageRef( + client: Sandboxes, + name: string, + imageRef: string, + resources: ClientCreateOptions['resources'], + requestId: string, +) { + const sandbox = await client.create( + { displayName: name, imageRef, resources }, + { timeoutMs: 300_000, idempotencyKey: requestId }, + ); + return sandbox.waitUntilRunning(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/send-values-the-api-accepts.md b/content/manuals/ai/sandboxes-api/cookbook/send-values-the-api-accepts.md new file mode 100644 index 0000000000..273e6f2ae7 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/send-values-the-api-accepts.md @@ -0,0 +1,110 @@ +--- +title: "Send values the API accepts" +linkTitle: "Send values the API accepts" +description: "Build a create request whose durations, counts, and flags mean what you intend, then read back the values the service settled on." +keywords: "cloud sandboxes, sandboxes api, send values the api accepts" +weight: 605 +params: + sidebar: + group: "Requests and responses" +--- + +Build SDK options without confusing an omitted value with an explicit false or zero. This matters for settings whose default is chosen by the service. + +Start with an [authenticated client](connect-to-cloud-with-a-bearer-token.md). Use the option types exported by your SDK so your editor can show the supported inputs. + +## Construct creation options {#1-construct-creation-options} + +Supply the image source and lifecycle settings, using the duration units required by your SDK. The example accepts seconds and converts them at the boundary. + +This example uses a managed image, which supplies its resource defaults. For a registry image instead, use the image-reference option and provide a supported CPU and memory pair. + +Preserve absence for an optional boolean when you want the service default. Explicit false is a choice, not a missing value. Validate user-supplied values before building the request. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return { + displayName: name, + image, + platform, + lifecycle: { timeoutMs: lifeSeconds * 1_000, autoResume }, +}; +``` + +
+Complete TypeScript example: values/build.ts + +```typescript +import type { ClientCreateOptions, Sandboxes } from '@docker/sandboxes'; + +export function createRequest( + name: string, + image: string, + lifeSeconds: number, + platform: ClientCreateOptions['platform'], + autoResume: boolean | undefined, +): ClientCreateOptions { + return { + displayName: name, + image, + platform, + lifecycle: { timeoutMs: lifeSeconds * 1_000, autoResume }, + }; +} + +export async function sendCreate( + client: Sandboxes, + request: ClientCreateOptions, +) { + const sandbox = await client.create(request, { timeoutMs: 300_000 }); + return sandbox.waitUntilRunning(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Read the effective values {#2-read-the-effective-values} + +Get the sandbox and inspect the reported platform, resources, and expiration. These describe what the service recorded, which can differ from omitted or defaulted input values. + +Keep the language's native numeric and duration representations. Avoid converting large integers through a floating-point type merely to display or serialize them. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return { + expiresAt: sandbox.effectiveFeatures?.timeouts?.expiresAt, + platform: sandbox.core.platform, + cpus: sandbox.core.resources?.cpus ?? undefined, +}; +``` + +
+Complete TypeScript example: values/read.ts + +```typescript +import type { Sandbox, Sandboxes } from '@docker/sandboxes'; + +export function reportedValues(sandbox: Sandbox) { + return { + expiresAt: sandbox.effectiveFeatures?.timeouts?.expiresAt, + platform: sandbox.core.platform, + cpus: sandbox.core.resources?.cpus ?? undefined, + }; +} + +export async function readSandbox(client: Sandboxes, name: string) { + return reportedValues(await client.get(name)); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/snapshot-and-fork-a-sandbox.md b/content/manuals/ai/sandboxes-api/cookbook/snapshot-and-fork-a-sandbox.md new file mode 100644 index 0000000000..94cc5b4940 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/snapshot-and-fork-a-sandbox.md @@ -0,0 +1,172 @@ +--- +title: "Snapshot and fork a sandbox" +linkTitle: "Snapshot and fork a sandbox" +description: "Capture the state of a running sandbox as a snapshot, then start a new sandbox from that snapshot." +keywords: "cloud sandboxes, sandboxes api, snapshot and fork a sandbox" +weight: 302 +params: + sidebar: + group: "Packaging and state" +--- + +Save a sandbox's state so you can start another sandbox from the same point later. A snapshot is separate from its source sandbox; restoring it creates a new sandbox with its own identity. + +Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md) and the resource name of a running sandbox. + +## Capture a snapshot {#1-capture-a-snapshot} + +Choose a display name, capture mode, and idempotency key. Use disk-only capture when you need the filesystem. Request memory only when your environment supports it and you need the running state. + +The example waits until capture finishes and returns a snapshot handle. Keep the returned name for restoration. + +A failed capture carries failure details; a timed-out wait does not prove the capture was cancelled. + +Snapshots can contain credentials and private project data. Restrict access to them, especially when capturing memory. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const snapshot = await sandbox.snapshot( + { + displayName: snapshotName, + captureMode: withMemory ? 'all' : 'disk', + }, + { idempotencyKey: requestId }, +); +return snapshot.waitUntilReady(); +``` + +
+Complete TypeScript example: snapshots/create.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function createSnapshot( + client: Sandboxes, + sandboxName: string, + snapshotName: string, + withMemory: boolean, + requestId: string, +) { + const sandbox = await client.get(sandboxName); + const snapshot = await sandbox.snapshot( + { + displayName: snapshotName, + captureMode: withMemory ? 'all' : 'disk', + }, + { idempotencyKey: requestId }, + ); + return snapshot.waitUntilReady(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Restore into another sandbox {#2-restore-into-another-sandbox} + +Use a ready snapshot's name and a display name for the new sandbox. The example waits for the restored sandbox to run. + +The call returns a sandbox handle, ready for running commands or transferring files. + +The source sandbox does not need to remain running. Restoration does not replace it. Save the new sandbox's name and delete it separately when you finish. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const sandbox = await snapshot.restore( + { displayName: newSandboxName }, + { timeoutMs: 300_000, idempotencyKey: requestId }, +); +return sandbox.waitUntilRunning(); +``` + +
+Complete TypeScript example: snapshots/restore.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function restoreSnapshot( + client: Sandboxes, + snapshotName: string, + newSandboxName: string, + requestId: string, +) { + const snapshot = await client.snapshots.get(snapshotName); + const sandbox = await snapshot.restore( + { displayName: newSandboxName }, + { timeoutMs: 300_000, idempotencyKey: requestId }, + ); + return sandbox.waitUntilRunning(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Find saved snapshots {#3-find-saved-snapshots} + +List snapshots for the source sandbox. The example follows all pages and returns summaries. Read a selected snapshot before restoring it if you need its current status or complete metadata. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +return client.snapshots.all({ sandbox: sandboxName }).collect(); +``` + +
+Complete TypeScript example: snapshots/list.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function listSnapshots( + client: Sandboxes, + sandboxName: string, +) { + return client.snapshots.all({ sandbox: sandboxName }).collect(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Delete a snapshot {#4-delete-a-snapshot} + +Delete through a handle when you no longer need that restore point. Deletion removes the snapshot, not sandboxes that have already been restored from it. Snapshot deletion is refused while a sandbox restored from it is still running. + +Deleting the source sandbox and deleting its snapshots are separate cleanup steps. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +await snapshot.delete(); +``` + +
+Complete TypeScript example: snapshots/delete.ts + +```typescript +import type { Snapshot } from '@docker/sandboxes'; + +export async function deleteSnapshot(snapshot: Snapshot) { + await snapshot.delete(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/stop-and-restart-a-sandbox.md b/content/manuals/ai/sandboxes-api/cookbook/stop-and-restart-a-sandbox.md new file mode 100644 index 0000000000..e169871954 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/stop-and-restart-a-sandbox.md @@ -0,0 +1,80 @@ +--- +title: "Stop and restart a sandbox" +linkTitle: "Stop and restart a sandbox" +description: "Stop a running sandbox without deleting it, wait for the stop to settle, and start the same sandbox again when you need it." +keywords: "cloud sandboxes, sandboxes api, stop and restart a sandbox" +weight: 207 +params: + sidebar: + group: "Working in a sandbox" +--- + +Stop a sandbox when you want to keep its disk but pause its execution. Start the same sandbox later to continue working with those files. + +Use a sandbox handle obtained by creation or by reading its resource name. Stopping is different from [deleting](delete-a-cloud-sandbox.md), which removes the sandbox. + +## Stop the sandbox {#1-stop-the-sandbox} + +Call stop and wait for the stopped state. The example returns a handle with the updated resource version. + +Wait completion confirms the state change. A timeout means your client stopped waiting; read the sandbox to learn whether the operation completed. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const changed = await sandbox.stop({ idempotencyKey: requestId }); +return changed.waitUntilStopped(); +``` + +
+Complete TypeScript example: lifecycle/stop.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function stopSandbox(sandbox: Sandbox, requestId: string) { + const changed = await sandbox.stop({ idempotencyKey: requestId }); + return changed.waitUntilStopped(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Start it again {#2-start-it-again} + +Do not reuse a handle whose version predates the stop. + +Start the stopped sandbox and wait for it to run. As with stopping, the call returns a handle. + +Do not assume that a process connection from before the stop remains usable. [Find an existing process](find-a-process-you-lost-track-of.md) or start the work again as appropriate for the application. + +A stopped sandbox still exists. Delete it when you no longer need its disk, and clean up separate snapshots or volumes only when their data is no longer needed. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const changed = await sandbox.start({ idempotencyKey: requestId }); +return changed.waitUntilRunning(); +``` + +
+Complete TypeScript example: lifecycle/start.ts + +```typescript +import type { Sandbox } from '@docker/sandboxes'; + +export async function startSandbox(sandbox: Sandbox, requestId: string) { + const changed = await sandbox.start({ idempotencyKey: requestId }); + return changed.waitUntilRunning(); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/what-your-workload-starts-with.md b/content/manuals/ai/sandboxes-api/cookbook/what-your-workload-starts-with.md new file mode 100644 index 0000000000..f6b41ac79d --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/what-your-workload-starts-with.md @@ -0,0 +1,136 @@ +--- +title: "What your workload starts with" +linkTitle: "What your workload starts with" +description: "Read the environment variables every command in a sandbox starts with, and see which source wins when two of them set the same name." +keywords: "cloud sandboxes, sandboxes api, what your workload starts with" +weight: 209 +params: + sidebar: + group: "Working in a sandbox" +--- + +Inspect the environment a process receives and override a value for one command. This helps diagnose a missing workspace path or configuration setting. + +Use a running sandbox handle. The environment can contain credentials, so do not dump the full result into logs or send it to an untrusted client. + +## Read the process environment {#1-read-the-process-environment} + +Run `env` and parse its output. Read `WORKSPACE_DIR` to locate the workspace rather than assuming a fixed path. + +This reports the environment seen by that process. It is a diagnostic example, not a way to retrieve stored secret values. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +export async function readFloor(sandbox: Sandbox) { + const result = requireSuccess( + await sandbox.processes.run({ args: ['env'] }, { timeoutMs: 300_000 }), + ); + return Object.fromEntries( + result.stdout + .split('\n') + .filter((line) => line.includes('=')) + .map((line) => { + const delimiter = line.indexOf('='); + return [line.slice(0, delimiter), line.slice(delimiter + 1)]; + }), + ); +} +``` + +
+Complete TypeScript example: runtime/read.ts + +```typescript +import { requireSuccess, type Sandbox } from '@docker/sandboxes'; + +export async function readFloor(sandbox: Sandbox) { + const result = requireSuccess( + await sandbox.processes.run({ args: ['env'] }, { timeoutMs: 300_000 }), + ); + return Object.fromEntries( + result.stdout + .split('\n') + .filter((line) => line.includes('=')) + .map((line) => { + const delimiter = line.indexOf('='); + return [line.slice(0, delimiter), line.slice(delimiter + 1)]; + }), + ); +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Override a variable for one command {#2-override-a-variable-for-one-command} + +Supply a value in the process request's environment map. The first command reads that override. A second command without the override reads the sandbox's original value. + +The override belongs to the process request and does not change the sandbox's environment for later commands. Use it for task-specific configuration. For provider credentials, prefer [stored secrets](get-a-stored-secret-into-a-sandbox.md). + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +const fromRequest = requireSuccess( + await sandbox.processes.run( + { + args: ['printenv', name], + env: { [name]: value }, + }, + { timeoutMs: 300_000 }, + ), +); +const fromSandbox = await sandbox.processes.run( + { + args: ['printenv', name], + }, + { timeoutMs: 300_000 }, +); +return { + fromRequest: fromRequest.stdout.trimEnd(), + fromSandbox: fromSandbox.stdout.trimEnd(), +}; +``` + +
+Complete TypeScript example: runtime/precedence.ts + +```typescript +import { requireSuccess, type Sandbox } from '@docker/sandboxes'; + +export async function overrideVariable( + sandbox: Sandbox, + name: string, + value: string, +) { + const fromRequest = requireSuccess( + await sandbox.processes.run( + { + args: ['printenv', name], + env: { [name]: value }, + }, + { timeoutMs: 300_000 }, + ), + ); + const fromSandbox = await sandbox.processes.run( + { + args: ['printenv', name], + }, + { timeoutMs: 300_000 }, + ); + return { + fromRequest: fromRequest.stdout.trimEnd(), + fromSandbox: fromSandbox.stdout.trimEnd(), + }; +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/cookbook/work-within-the-limits.md b/content/manuals/ai/sandboxes-api/cookbook/work-within-the-limits.md new file mode 100644 index 0000000000..0e854ca2b2 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/cookbook/work-within-the-limits.md @@ -0,0 +1,178 @@ +--- +title: "Work within the limits" +linkTitle: "Work within the limits" +description: "Choose a supported compute size, understand account quotas, and handle refusals without endless retries." +keywords: "cloud sandboxes, sandboxes api, work within the limits" +weight: 606 +params: + sidebar: + group: "Requests and responses" +--- + +Bound work so account limits and temporary capacity refusals do not turn into unbounded retries. + +Use an [authenticated client](connect-to-cloud-with-a-bearer-token.md). Account limits are separate from SDK configuration; changing the client does not grant additional capacity. + +### Choose a compute size + +Cloud Sandboxes uses fixed CPU and memory pairs as billing shapes: + +| Size | vCPUs | Memory | SDK memory value (MiB) | +| ------ | ----: | -----: | ---------------------: | +| Micro | 1 | 2 GiB | 2048 | +| Small | 2 | 4 GiB | 4096 | +| Medium | 4 | 8 GiB | 8192 | +| Large | 8 | 16 GiB | 16384 | +| XL | 16 | 32 GiB | 32768 | + +Choose a size by name, for example, `resources: 'small'`. The supported names are `micro`, `small`, `medium`, `large`, and `xl`. Kit launches default to Small when you omit resources. Explicit settings take precedence. + +You can still supply both CPU and memory directly. Use a supported pair; 1 CPU with 1024 MiB is not supported in Cloud Sandboxes. A saved image supplies its own resources, so do not override them when creating from that image. Your account and available capacity determine whether a request can be accepted. Check your Docker billing terms for prices. + +### Understand account quotas + +The default limits are 10 concurrent sandboxes, 50 stored sandboxes, 100 volumes, 100 secrets, and 3 images being prepared at once. These are account-wide defaults, not a separate allowance per user. Your account can have different limits; confirm them with Docker before sizing a large workload. + +Stopping an ordinary sandbox frees its concurrency slot but leaves it in stored usage. Starting or resuming it needs a slot again. An always-on sandbox, configured to restart automatically on timeout, retains its concurrency reservation even while stopped. Delete sandboxes you no longer need to release stored usage. + +## Count existing sandboxes {#1-count-existing-sandboxes} + +Walk all sandbox pages and count resources visible to you. This gives your application a usage observation, not a reservation or authoritative quota balance. Other users can hold resources in the same account that your list does not show. + +Another caller can create a sandbox immediately afterward. Treat the create response as the final decision, even when your count appears below a planned limit. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +let count = 0; +for await (const sandbox of client.all({ pageSize })) { + if (sandbox.name) count++; +} +return count; +``` + +
+Complete TypeScript example: limits/budget.ts + +```typescript +import type { Sandboxes } from '@docker/sandboxes'; + +export async function countSandboxes(client: Sandboxes, pageSize: number) { + let count = 0; + for await (const sandbox of client.all({ pageSize })) { + if (sandbox.name) count++; + } + return count; +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} + +## Back off after a refusal {#2-back-off-after-a-refusal} + +Use the server's retry delay when supplied and keep the same idempotency key for the same create request. Bound the number of attempts and give the overall operation a deadline. + +The example owns the retry loop, disables automatic SDK retries, and sets an overall timeout. Do not layer two retry policies without accounting for their combined attempts and deadlines. + +A quota refusal may need cleanup or an account change rather than another immediate request. Stop after the bound, report the failure, and keep any accepted sandbox identity available for inspection. + +{{< tabs >}} +{{< tab name="TypeScript" >}} + +```typescript +for (let attempt = 0; attempt < attempts; attempt++) { + try { + return await client.kits.launch( + 'shell', + { displayName, resources: { cpus: 2, memoryMib: 4096 } }, + { + idempotencyKey: requestId, + timeoutMs: 300_000, + signal, + maxRetries: 0, + }, + ); + } catch (error) { + if ( + !(error instanceof RequestError) || + error.raw.code !== 'resourceExhausted' || + attempt + 1 === attempts + ) + throw error; + await pause(retryAfter(error, fallbackMs), undefined, { signal }); + } +} +throw new Error('attempt count was validated'); +``` + +
+Complete TypeScript example: limits/backoff.ts + +```typescript +import { setTimeout as pause } from 'node:timers/promises'; +import { + RequestError, + type Sandbox, + type Sandboxes, +} from '@docker/sandboxes'; + +export async function createWithBackoff( + client: Sandboxes, + displayName: string, + attempts: number, + fallbackMs: number, + requestId: string, +): Promise { + if (!Number.isInteger(attempts) || attempts < 1) + throw new Error(`attempts must be at least 1, got ${attempts}`); + const signal = AbortSignal.timeout(300000); + for (let attempt = 0; attempt < attempts; attempt++) { + try { + return await client.kits.launch( + 'shell', + { displayName, resources: { cpus: 2, memoryMib: 4096 } }, + { + idempotencyKey: requestId, + timeoutMs: 300_000, + signal, + maxRetries: 0, + }, + ); + } catch (error) { + if ( + !(error instanceof RequestError) || + error.raw.code !== 'resourceExhausted' || + attempt + 1 === attempts + ) + throw error; + await pause(retryAfter(error, fallbackMs), undefined, { signal }); + } + } + throw new Error('attempt count was validated'); +} + +export function retryAfter( + error: RequestError, + fallbackMs: number, +): number { + const header = error.retryAfter?.trim(); + if (header) { + const delay = /^\d+(?:\.\d+)?$/.test(header) + ? Number(header) * 1000 + : /^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)[a-z]*,?\s/.test(header) + ? Math.max(0, Date.parse(header) - Date.now()) + : NaN; + if (Number.isFinite(delay) && delay >= 0) return delay; + } + return error.decoded.retryDelayMs ?? fallbackMs; +} +``` + +
+ +{{< /tab >}} +{{< /tabs >}} diff --git a/content/manuals/ai/sandboxes-api/errors.md b/content/manuals/ai/sandboxes-api/errors.md new file mode 100644 index 0000000000..6a07fa223e --- /dev/null +++ b/content/manuals/ai/sandboxes-api/errors.md @@ -0,0 +1,142 @@ +--- +title: Errors and retries +description: Handle Docker Sandboxes API errors, recover from failed waits, and retry requests without duplicating work. +keywords: docker sandboxes API errors, REST errors, API retries, idempotency keys, rate limits +weight: 40 +aliases: + - /ai/sandboxes/api/errors/ +--- + +> [!NOTE] +> The Docker Sandboxes API and SDK are experimental. Features, interfaces, +> and behavior may change. + +Before retrying a failed request, check whether the service already started +the work. For example, a create request can succeed even if your application +loses the response. Retrying without checking can create a second sandbox. + +## Read an error response + +API errors contain a `code`, a `message`, and optional typed `details`. Use the +code to decide how to respond. Several codes share an HTTP status, so the +status alone might not explain the failure. + +| Code | What to do | +| --- | --- | +| `invalidArgument` | Correct the malformed request or unsupported value before retrying. | +| `unauthenticated` | Obtain a valid credential to replace the missing, invalid, or expired one. | +| `permissionDenied` | Check that your credentials have permission for the action. | +| `notFound` | Check the resource name and request URL. Cloud also returns this code for routes it doesn't serve. | +| `failedPrecondition` | Check the resource state, required features, and any `If-Match` header. | +| `resourceExhausted` | Check the error details for a quota, rate limit, or request-size limit. | +| `unimplemented` | Check whether the backend supports the requested feature. | +| `unavailable` | Retry after a delay, once you know the retry won't duplicate work. | + +For more detail, inspect `google.rpc.ErrorInfo` in the error's `details`, if +present. Use its `reason` and `domain` fields together with the error code to +handle specific causes. Avoid matching the free-form message text, and +handle responses even when they include detail types you don't recognize. + +## Recover from a failed wait + +If the API returns HTTP 202, the work is still in progress. Keep reading the +resource until it reaches the state you need or fails. The resource's `failure` +field describes a failure that occurs after the initial request succeeds. + +If a wait times out or is canceled, the action can still finish. Inspect the +resource before trying again or deleting it. SDK wait helpers report +`WaitError`, which includes the last resource the client received and any +failure details. + +If the client never received a resource, retry the original request with its +idempotency key to recover the response. See +[Retry without duplicating work](#retry-without-duplicating-work). + +## Retry without duplicating work + +An idempotency key identifies one request, so the service can return its +original response if you send it again. For example, repeating a sandbox +creation request with the same key returns the first sandbox instead of +creating another one. + +The SDK generates a key for each supported mutation unless you supply one. +For direct API calls, include an `Idempotency-Key` header in the original +request. Keep the key and the exact request, including any `If-Match` value, +for retries. + +The service keeps accepted results for at least 24 hours. A replay returns +the original response, so read the resource afterward to check its latest +state. + +Use the same key only when repeating the same request. Changing the request +under that key causes an error, and using a different key submits another +action. Send the header only for operations that support it: + +| Operations | `Idempotency-Key` | +| --- | --- | +| Create a sandbox, image, process, port, snapshot, secret, or volume | Optional | +| Restore a snapshot | Optional | +| Start, stop, or delete a sandbox | Optional | +| Update a secret | Optional | +| Update a sandbox | Required | + +Other operations don't accept an idempotency key. + +Once you know a create request succeeded, poll the returned resource to wait +for completion. Retry only when you need to recover from a failure or a lost +response. For temporary failures, wait between retries and limit the number +of attempts. + +Process creation also supports an idempotency key. Recover the creation +response with the same key before starting another process. Process input, +signals, and file writes aren't covered by that key. Check the outcome +before repeating those actions. + +## Account for SDK retries + +The SDK makes up to two additional attempts for eligible transient failures. +If your application manages its own retry loop, set `maxRetries: 0` for the +call to disable automatic retries. Combining both policies can produce more +attempts than you intended. + +Automatic retries within a call reuse its idempotency key. When retrying from +your application, pass the original key in the call options as +`idempotencyKey`. Otherwise, a separate create call can generate a different +key and create another sandbox. + +Limit both retry attempts and elapsed time, and honor server retry delays. +See [Request rate limits](limits.md#request-rate-limits) for how rate limits +differ from resource quotas. + +## Handle concurrent changes + +To avoid changing a resource that someone else has modified, send its `etag` +in the `If-Match` header. An etag identifies the version of the resource you +read. Operations such as sandbox updates and deletion require this header. +The SDK sends the etag stored in the resource object you're using. + +For direct API requests, pass the etag exactly as returned, including its +quotes. A missing required header returns HTTP 428, and a stale etag returns +HTTP 412. + +If the etag is stale, read the resource again and decide whether your change +is still appropriate. In the SDK, use the object returned by `refresh()` for +the next operation. The original object still has the old etag. Use a +different idempotency key for a request with an updated etag. + +## Check command results + +An API request can succeed even when the command it runs fails. Check the +command result's exit code and output separately from request and wait errors. +A nonzero exit code reports a command failure. + +## Set a timeout for commands + +To limit how long `processes.run()` waits for a command, pass `timeoutMs` in +the second argument. For example, +`sandbox.processes.run(input, { timeoutMs: 300_000 })` waits up to five minutes. +Timing out or canceling the call stops local waiting. It doesn't kill the +process in the sandbox. + +Without `timeoutMs`, the overall run has no timeout. Individual requests to +create the process and read its output have a 30-second timeout. diff --git a/content/manuals/ai/sandboxes-api/get-started.md b/content/manuals/ai/sandboxes-api/get-started.md new file mode 100644 index 0000000000..4e2ac70ea6 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/get-started.md @@ -0,0 +1,125 @@ +--- +title: Run your first cloud sandbox +linkTitle: Get started +description: Use the TypeScript SDK to create a cloud sandbox, run a command, and delete the sandbox. +keywords: Docker Sandboxes API tutorial, cloud sandbox TypeScript, create sandbox, sandbox SDK quickstart +weight: 10 +aliases: + - /ai/sandboxes/api/get-started/ +--- + +> [!NOTE] +> The Docker Sandboxes API and SDK are experimental. Features, interfaces, +> and behavior may change. + +Create a cloud sandbox, run a command inside it, and delete it using the Docker +Sandboxes TypeScript SDK. This tutorial uses the bundled `shell` kit to print +`Hello from Docker Sandboxes`. You don't need an AI agent or a model provider +API key to run it. + +## Prerequisites + +To follow this tutorial, you need: + +- A Docker account with an active + [Docker Agentic Platform subscription](/manuals/agentic-platform/signup.md#activate-cloud-access) +- Node.js 20 or later and npm + +## Create a project + +Create a directory and initialize a Node.js project: + +```console +$ mkdir sandboxes-api-tutorial +$ cd sandboxes-api-tutorial +$ npm init --yes +$ npm pkg set type=module +``` + +Install the SDK and TypeScript tooling: + +```console +$ npm install @docker/sandboxes +$ npm install --save-dev tsx typescript @types/node +``` + +## Create and use a sandbox + +A kit supplies the sandbox's image and configuration for an agent or tool. +The `shell` kit provides the environment for this example. + +The SDK launches it with the default `small` compute size: two CPUs and +4 GiB of memory. Cloud compute is billed to your subscription. See +[Compute sizes and limits](limits.md) for other sizes. + +Create a file named `index.ts` with the following code. The program prompts +you to sign in, creates a sandbox, runs a command, and deletes the sandbox. + +```typescript +import { oauth, Sandboxes } from '@docker/sandboxes'; + +const client = new Sandboxes({ + auth: oauth({ + onVerification({ verificationUriComplete, verificationUri, userCode }) { + console.log(`Open ${verificationUriComplete ?? verificationUri}`); + console.log(`Verification code: ${userCode}`); + }, + }), +}); + +try { + const sandbox = await client.kits.launchAndWait('shell'); + console.log('Sandbox:', sandbox.name); + + const result = await sandbox.processes.run( + { args: ['echo', 'Hello from Docker Sandboxes'] }, + { timeoutMs: 300_000 }, + ); + console.log(result.stdout.trim()); + + const latest = await sandbox.refresh(); + const deleting = await latest.delete({ force: true }); + await deleting?.waitUntilDeleted(); + console.log('Deleted', sandbox.name); +} finally { + await client.close(); +} +``` + +`kits.launchAndWait` creates the sandbox and waits until it's running. +`processes.run` runs the command and collects its output. +The program then reads the sandbox's latest state, deletes it, and waits for +deletion to finish. The `force` option permits deletion while the sandbox is +running. + +The SDK handles sign-in, access tokens, and the connection to the sandbox. +Closing the client releases its local resources. + +## Run the program + +Run the program with `tsx`: + +```console +$ npx tsx index.ts +``` + +Open the printed verification URL and sign in with the Docker account that +has your cloud subscription. The program continues after sign-in. On success, +it prints `Hello from Docker Sandboxes`, then confirms sandbox deletion. + +For CI jobs and other unattended applications, use +[PAT authentication](authentication.md#authenticate-automation-with-a-pat). + +If the program stops after printing the sandbox name, retrieve the sandbox +with `client.get(name)` and delete it when you're finished. Closing the client +doesn't delete the sandbox. + +## Next steps + +- Explore the [SDK cookbook](cookbook/_index.md) for examples you can use in + your application. +- If you use a kit that clones a repository or installs tools, + [wait for kit setup](concepts.md#wait-for-kit-setup) before using its results. +- Read [API concepts](concepts.md) to learn about resource names, lifecycle + states, and supported Cloud options. +- Review [Errors and retries](errors.md) before adding recovery logic. diff --git a/content/manuals/ai/sandboxes-api/install.md b/content/manuals/ai/sandboxes-api/install.md new file mode 100644 index 0000000000..7c95a20845 --- /dev/null +++ b/content/manuals/ai/sandboxes-api/install.md @@ -0,0 +1,43 @@ +--- +title: Install the Docker Sandboxes SDK +linkTitle: Install the SDK +description: Install and use the Docker Sandboxes SDK in your JavaScript or TypeScript application. +keywords: docker sandboxes SDK, TypeScript SDK, JavaScript, npm, install sandbox SDK +weight: 5 +aliases: + - /ai/sandboxes-api/sdks/ + - /ai/sandboxes/api/sdks/ +--- + +> [!NOTE] +> The Docker Sandboxes API and SDK are experimental. Features, interfaces, +> and behavior may change. + +Use the Docker Sandboxes SDK to create and manage cloud sandboxes from +JavaScript or TypeScript. The SDK includes methods for running commands, +transferring files, and waiting for a sandbox to start or stop. + +You don't need the Docker CLI to use the SDK. + +## Install the SDK + +With Node.js 20 or later, install the SDK in your project: + +```console +$ npm install @docker/sandboxes +``` + +Import `Sandboxes` from `@docker/sandboxes`. + +## Connect to Cloud Sandboxes + +Before connecting, [activate a Docker Agentic Platform subscription](/manuals/agentic-platform/signup.md#activate-cloud-access) +for your Docker account. + +Configure your client with browser sign-in for interactive use or a personal +access token for automation. The SDK supplies the service URL and manages +access tokens. See [Authentication and authorization](authentication.md) for +setup instructions. + +Follow [Run your first cloud sandbox](get-started.md) for a complete TypeScript +example that creates a sandbox, runs a command, and deletes it. diff --git a/content/manuals/ai/sandboxes-api/limits.md b/content/manuals/ai/sandboxes-api/limits.md new file mode 100644 index 0000000000..bb448905cd --- /dev/null +++ b/content/manuals/ai/sandboxes-api/limits.md @@ -0,0 +1,88 @@ +--- +title: Compute sizes and limits +description: Choose a Cloud Sandboxes compute size and plan for account quotas and request rate limits. +keywords: cloud sandboxes, compute sizes, CPU, memory, quotas, rate limits +weight: 50 +aliases: + - /ai/sandboxes/api/limits/ +--- + +> [!NOTE] +> The Docker Sandboxes API and SDK are experimental. Features, interfaces, +> and behavior may change. + +Choose a compute size for each sandbox and keep your application's resource +usage within your account's quotas. Request rate limits also constrain how +quickly your application can send API requests. + +## Compute sizes + +Cloud Sandboxes supports these CPU and memory pairs: + +| SDK size name | CPUs | Memory | Memory in MiB | +| --- | ---: | ---: | ---: | +| `micro` | 1 | 2 GiB | 2048 | +| `small` | 2 | 4 GiB | 4096 | +| `medium` | 4 | 8 GiB | 8192 | +| `large` | 8 | 16 GiB | 16384 | +| `xl` | 16 | 32 GiB | 32768 | + +The kit launch helpers default to `small` when you omit `resources`. To select +another size, pass its name: + +```typescript +const sandbox = await client.kits.launchAndWait('shell', { + resources: 'medium', +}); +``` + +For a registry image, specify a size in `client.create()`, such as +`resources: 'small'`. You can also pass an explicit CPU and memory pair: +`resources: { cpus: 2, memoryMib: 4096 }`. + +Named sizes are an SDK convenience. In direct REST requests, supply both +`resources.cpus` and `resources.memoryMib`. CPU and memory aren't independent +settings: for example, 1 CPU with 1024 MiB is not a supported pair. + +When creating from an existing image resource with `image`, omit resource +settings because the image supplies them. + +Your account access and available capacity determine whether a request can +be accepted. See [Billing](/manuals/agentic-platform/signup.md#billing) for +pricing and usage information. + +## Account quotas + +The default quotas apply across an account: + +| Resource | Default limit | +| --- | ---: | +| Concurrent sandboxes | 10 | +| Stored sandboxes | 50 | +| Volumes | 100 | +| Secrets | 100 | +| Images being prepared at the same time | 3 | + +Your account can have different quotas. Confirm your account's limits with +Docker before planning a workload that depends on a particular allowance. + +Stopping a sandbox releases its concurrency slot unless the sandbox is +configured as always-on. Restarting a stopped sandbox requires a concurrency +slot. A stopped sandbox still counts toward the stored sandbox quota. +Delete sandboxes you no longer need to reduce stored usage. + +Handle quota errors even if you checked usage before creating a resource. +Other applications can consume the remaining allowance between requests. +Reduce concurrency or remove unused resources before retrying. + +## Request rate limits + +Rate limits control how quickly you can send requests and can vary by +operation. When a request reaches a rate limit, wait before retrying and +honor any delay specified by the server. Limit concurrent requests and set +bounds on retry attempts and elapsed time. + +Check the error details to distinguish a rate limit from a resource quota. +Waiting can resolve a rate limit, but exceeding a quota requires reducing +resource usage. See [Errors and retries](errors.md) for how to retry without +duplicating work. diff --git a/content/manuals/ai/sandboxes-api/reference.md b/content/manuals/ai/sandboxes-api/reference.md new file mode 100644 index 0000000000..3ded7e620a --- /dev/null +++ b/content/manuals/ai/sandboxes-api/reference.md @@ -0,0 +1,13 @@ +--- +title: Docker Sandboxes API reference +linkTitle: API reference +description: HTTP operations, request fields, and responses for the Docker Sandboxes API. +keywords: Docker Sandboxes API, REST API, OpenAPI, API reference +weight: 70 +build: + render: never + list: local +params: + sidebar: + goto: /reference/api/sandboxes/latest/ +--- diff --git a/content/manuals/ai/sandboxes/_index.md b/content/manuals/ai/sandboxes/_index.md index 0c95d3e92d..75082e7992 100644 --- a/content/manuals/ai/sandboxes/_index.md +++ b/content/manuals/ai/sandboxes/_index.md @@ -37,6 +37,9 @@ $ sbx run claude See the [get started guide](get-started.md) for a first-session walkthrough, or jump to the [usage guide](usage.md) for basic commands. +To create and manage cloud sandboxes from your application, see +[Sandboxes API and SDK](../sandboxes-api/_index.md). + ## Learn more - [Agents](agents/) — supported agents and per-agent configuration diff --git a/content/reference/_index.md b/content/reference/_index.md index 67c45581e6..c62db0df7f 100644 --- a/content/reference/_index.md +++ b/content/reference/_index.md @@ -3,6 +3,7 @@ title: Reference documentation linkTitle: Reference layout: wide description: Find reference documentation for the Docker platform’s various APIs, CLIs, and file formats +keywords: Docker reference, API, CLI, file formats params: icon: command-line notoc: true @@ -30,6 +31,10 @@ params: icon: cpu-chip link: /reference/cli/dockerd/ grid_apis: + - title: Sandboxes API (Experimental) + description: Create cloud sandboxes, run processes, and manage sandbox resources. + icon: code-bracket + link: /reference/api/sandboxes/latest/ - title: Engine API description: The main API for Docker, provides programmatic access to a daemon. icon: code-bracket diff --git a/content/reference/api/_content.gotmpl b/content/reference/api/_content.gotmpl index b83973f770..be5cc328dc 100644 --- a/content/reference/api/_content.gotmpl +++ b/content/reference/api/_content.gotmpl @@ -15,6 +15,9 @@ {{- if eq $api.id "ai-governance" }} {{ $aliases = slice "/reference/api/ai-governance/" "/ai/sandboxes/governance/api/" }} {{ end -}} + {{- if eq $api.id "sandboxes" }} + {{ $aliases = slice "/reference/api/sandboxes/" }} + {{ end -}} {{- $.AddPage (dict "linkTitle" "Latest" "path" $path "url" $api.url "aliases" $aliases "kind" "section" "title" (printf "%s API %v" $api.title $api.version) "description" (printf "%s HTTP API reference, version %v." $api.title $api.version) "layout" "api-docs" "weight" 1 "params" (dict "view" "overview" "apiID" $api.id "keywords" (slice "API" $api.product))) -}} {{- range $op := $api.operations -}} {{- $.AddPage (dict "path" (printf "%s/operations/%s" $path (crypto.SHA256 $op.id)) "url" $op.url "title" $op.summary "description" (printf "%s %s — %s API %v." $op.method $op.path $api.title $api.version) "layout" "api-docs" "params" (dict "view" "operation" "apiID" $api.id "operationID" $op.id "keywords" (slice $op.method $op.path $op.id))) -}} diff --git a/content/reference/api/sandboxes/_index.md b/content/reference/api/sandboxes/_index.md new file mode 100644 index 0000000000..10d1cbdc29 --- /dev/null +++ b/content/reference/api/sandboxes/_index.md @@ -0,0 +1,7 @@ +--- +title: Docker Sandboxes API +description: HTTP API reference for Docker Cloud Sandboxes. +keywords: Docker Sandboxes, API reference, OpenAPI +build: + render: never +--- diff --git a/content/reference/api/sandboxes/api.yaml b/content/reference/api/sandboxes/api.yaml new file mode 100644 index 0000000000..ed2b61a614 --- /dev/null +++ b/content/reference/api/sandboxes/api.yaml @@ -0,0 +1,9307 @@ +openapi: 3.2.0 +info: + title: Docker Sandboxes API + version: v1 + description: |- + Use this API to create sandboxes, run processes, work with files, and manage sandbox resources. Start with a kit through the SDK, or use the HTTP operations here directly. + + Management requests use an Authorization bearer token. File and process requests go to the sandbox endpoint in core.endpoint.uri and require a short-lived scoped endpoint credential, not the management token. The SDK obtains that credential for you. Each operation also checks its declared permissions. Direct Unix socket access uses the operating system's socket access controls instead of HTTP authentication. Interactive WebSocket operations describe their supported credential transports separately. + + The Docker Cloud API base URL is https://connect.docker.com/sandboxes. Append each /v1 path without removing the base URL's path prefix. For sandbox endpoint operations, use the sandbox endpoint as the base URL instead. + + Operations list their success and error responses. A 202 response means work is still in progress; read the resource until it reaches the expected state. Errors contain a stable code, a message, and optional details. Inspect the code as well as the HTTP status. + + Send the current ETag in If-Match when an operation requires it. Operations that accept Idempotency-Key use that HTTP header to identify retries: the same key and payload return the first result; a different payload fails. Keys are retained for at least 24 hours. + + Lists use pageSize and pageToken. Continue with nextPageToken until it is empty. Supported filters and ordering are listed on each operation. Resource names contain immutable identifiers; displayName is a label. A 64-bit integer is returned as a JSON string; inputs accept a number or a string. + contact: + name: Docker Cloud Sandboxes + url: https://github.com/docker/sandboxes-api +servers: + - url: https://connect.docker.com/sandboxes + description: The API base URL. Append the /v1 paths while preserving this URL's path prefix. +paths: + /v1/files: + get: + tags: + - Files + summary: List lists directory entries by path ascending. + description: List lists directory entries by path ascending. + operationId: list + parameters: + - name: path + in: query + description: path is the directory path. + required: true + schema: + type: string + title: path + minLength: 1 + - name: pageSize + in: query + description: |- + page_size is an optional page size. + + Omitted or zero uses the backend default. Unless the operation states otherwise, page-size limits and handling of larger requests are backend-specific; use the backend support guide. Continue with nextPageToken until it is empty. + schema: + type: integer + title: page_size + required: false + - name: pageToken + in: query + description: page_token is an optional continuation token. + schema: + type: string + title: page_token + required: false + - name: orderBy + in: query + description: |- + order_by is a single order field with optional direction; the portable field is + path. + schema: + type: string + title: order_by + required: false + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/FilesListResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesFilesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + delete: + tags: + - Files + summary: Remove deletes a path. + description: |- + Remove deletes a path. Removing an absent path succeeds. A nonrecursive + removal of a nonempty directory returns FAILED_PRECONDITION unchanged. + A recursive removal that stops partway still answers OK: check + RemoveResponse.failed_path, not the operation status. Removed paths stay removed. + operationId: remove + parameters: + - name: path + in: query + description: path is the sandbox path to remove. + required: true + schema: + type: string + title: path + minLength: 1 + - name: recursive + in: query + description: recursive removes directory contents when true. + schema: + type: boolean + title: recursive + required: false + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/FilesRemoveResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesFilesWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/files/content: + get: + tags: + - Files + summary: ReadFile returns raw file content over HTTP; Stat exposes file metadata. + description: |- + ReadFile returns raw file content over HTTP; Stat exposes file metadata. + Content above the unary message cap is refused, never truncated. + operationId: readFile + parameters: + - name: path + in: query + description: path is the sandbox path of a regular file. + required: true + schema: + type: string + title: path + minLength: 1 + responses: + "200": + description: Success + content: + application/octet-stream: + schema: + contentMediaType: application/octet-stream + headers: + Content-Length: + required: true + description: Exact byte count of the complete file; this response does not use chunked transfer encoding. + schema: + type: integer + minimum: 0 + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesFilesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + x-sbx-http-transfer: + response: + mediaType: application/octet-stream + bytesField: data + x-sbx-request-schema: + $ref: "#/components/schemas/FilesReadFileRequest" + x-sbx-response-schema: + $ref: "#/components/schemas/FilesReadFileResponse" + put: + tags: + - Files + summary: WriteFile accepts raw content with path and mode query parameters. + description: |- + WriteFile accepts raw content with path and mode query parameters. + It replaces the complete target state and carries no request_id. + operationId: writeFile + parameters: + - name: path + in: query + description: path is the sandbox path. + required: true + schema: + type: string + title: path + minLength: 1 + - name: mode + in: query + description: |- + mode carries Unix permission bits. Upload applies mode & 0777, or 0644 when + that value is zero, without subtracting umask; Download reports stored bits. + schema: + type: integer + title: mode + required: false + requestBody: + required: true + content: + application/octet-stream: + schema: + contentMediaType: application/octet-stream + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/FilesWriteFileResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesFilesWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + x-sbx-http-transfer: + request: + mediaType: application/octet-stream + bytesField: data + x-sbx-request-schema: + $ref: "#/components/schemas/FilesWriteFileRequest" + x-sbx-response-schema: + $ref: "#/components/schemas/FilesWriteFileResponse" + /v1/files/directories: + put: + tags: + - Files + summary: Mkdir creates a directory. + description: |- + Mkdir creates a directory. Without parents, a missing parent returns + NOT_FOUND without creating directories. A repeated Mkdir converges when + the leaf already exists as a directory. A converged call leaves + the directory's mode unchanged on every request. + operationId: mkdir + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/FilesMkdirRequest" + required: true + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/FilesMkdirResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesFilesWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/files/download: + get: + tags: + - Files + summary: Download streams file headers, bytes, and per-file errors. + description: |- + Download streams file headers, bytes, and per-file errors. A path that + leaves the jail through a symlink, or names a non-regular object, reports + a FileError with a error reason instead of reading through it. + operationId: download + parameters: + - name: paths + in: query + description: |- + paths naming stable regular files or absent entries at canonical absolute paths + yield contiguous results in request order, including duplicates. + required: true + schema: + type: array + items: + type: string + title: paths + minItems: 1 + responses: + "200": + description: |- + Success + + Read metadata followed by one continuous raw content part. JSON controls are separate parts with Content-Type application/json; binary content uses application/octet-stream. A final stream-error part carries the shared Error for a whole-stream failure. A clean closing delimiter with no stream-error is required for success; aborted connections are failures. + content: + multipart/mixed: + itemSchema: + anyOf: + - $ref: "#/components/schemas/FileHeader" + - contentMediaType: application/octet-stream + - $ref: "#/components/schemas/FileError" + - $ref: "#/components/schemas/Error" + itemEncoding: + contentType: application/json, application/octet-stream + headers: + Content-Disposition: + required: true + schema: + type: string + enum: + - attachment; name=metadata + - attachment; name=content + - attachment; name=error + - attachment; name=stream-error + x-sbx-framing: + description: Read metadata followed by one continuous raw content part. JSON controls are separate parts with Content-Type application/json; binary content uses application/octet-stream. A final stream-error part carries the shared Error for a whole-stream failure. A clean closing delimiter with no stream-error is required for success; aborted connections are failures. + parts: + metadata: + contentType: application/json + schema: + $ref: "#/components/schemas/FileHeader" + content: + contentType: application/octet-stream + schema: + contentMediaType: application/octet-stream + error: + contentType: application/json + schema: + $ref: "#/components/schemas/FileError" + stream-error: + contentType: application/json + schema: + $ref: "#/components/schemas/Error" + repeatedItems: true + requiresClosingDelimiter: true + requiresTerminal: false + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesFilesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + x-sbx-http-transfer: + response: + mediaType: multipart/mixed + bytesField: data + metadataField: header + errorField: error + repeatedItems: true + x-sbx-request-schema: + $ref: "#/components/schemas/FilesDownloadRequest" + x-sbx-response-schema: + $ref: "#/components/schemas/FilesDownloadResponse" + /v1/files/move: + post: + tags: + - Files + summary: Move renames or moves a path. + description: |- + Move renames or moves a path. The destination is exact: an existing regular + file is replaced, the source basename is never appended, and a directory + destination is refused unchanged. A source or destination that leaves the jail + through a symlink refuses with FAILED_PRECONDITION and a error reason. + operationId: move + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/FilesMoveRequest" + required: true + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/FilesMoveResponse" + "409": + description: |- + The path resolves through a symlink that does not resolve to a target inside the sandbox jail. + + The path names an existing socket, FIFO, device, or other non-regular object where a regular file is required. + + `from` names a regular file and `to` an existing directory, which `Move` refuses rather than moving the file into it. + + `from` names a directory and `to` an existing path, which `Move` refuses rather than replacing. + + `from` and `to` are on different filesystems, and `Move` never copies the contents to cross the boundary. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + path_authority_symlink_escape: + summary: The path resolves through a symlink that does not resolve to a target inside the sandbox jail. + value: + code: failedPrecondition + message: "path_authority_symlink_escape: The path resolves through a symlink that does not resolve to a target inside the sandbox jail." + path_authority_nonregular_object: + summary: The path names an existing socket, FIFO, device, or other non-regular object where a regular file is required. + value: + code: failedPrecondition + message: "path_authority_nonregular_object: The path names an existing socket, FIFO, device, or other non-regular object where a regular file is required." + move_destination_is_directory: + summary: "`from` names a regular file and `to` an existing directory, which `Move` refuses rather than moving the file into it." + value: + code: failedPrecondition + message: "move_destination_is_directory: `from` names a regular file and `to` an existing directory, which `Move` refuses rather than moving the file into it." + move_destination_occupied: + summary: "`from` names a directory and `to` an existing path, which `Move` refuses rather than replacing." + value: + code: failedPrecondition + message: "move_destination_occupied: `from` names a directory and `to` an existing path, which `Move` refuses rather than replacing." + move_crosses_filesystem: + summary: "`from` and `to` are on different filesystems, and `Move` never copies the contents to cross the boundary." + value: + code: failedPrecondition + message: "move_crosses_filesystem: `from` and `to` are on different filesystems, and `Move` never copies the contents to cross the boundary." + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: FAILED_PRECONDITION + description: The path resolves through a symlink that does not resolve to a target inside the sandbox jail. + reason: path_authority_symlink_escape + - code: FAILED_PRECONDITION + description: The path names an existing socket, FIFO, device, or other non-regular object where a regular file is required. + reason: path_authority_nonregular_object + - code: FAILED_PRECONDITION + description: "`from` names a regular file and `to` an existing directory, which `Move` refuses rather than moving the file into it." + reason: move_destination_is_directory + - code: FAILED_PRECONDITION + description: "`from` names a directory and `to` an existing path, which `Move` refuses rather than replacing." + reason: move_destination_occupied + - code: FAILED_PRECONDITION + description: "`from` and `to` are on different filesystems, and `Move` never copies the contents to cross the boundary." + reason: move_crosses_filesystem + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesFilesWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/files/stat: + get: + tags: + - Files + summary: Stat reads metadata for one path. + description: |- + Stat reads metadata for one path. + A metadata read has a different response from the file-content resource. + operationId: stat + parameters: + - name: path + in: query + description: path is the sandbox path. + required: true + schema: + type: string + title: path + minLength: 1 + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/FilesStatResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesFilesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/files/upload: + post: + tags: + - Files + summary: Upload streams one or more files. + description: |- + Upload streams one or more files. The first frame must be FileHeader. + Writing through a symlink that leaves the jail, or onto an existing + non-regular object, refuses with FAILED_PRECONDITION and a error reason. + operationId: upload + requestBody: + required: true + content: + multipart/form-data: + itemSchema: + oneOf: + - type: object + properties: + metadata: + $ref: "#/components/schemas/FileHeader" + required: + - metadata + additionalProperties: false + - type: object + properties: + content: + contentMediaType: application/octet-stream + required: + - content + additionalProperties: false + itemEncoding: + contentType: application/json, application/octet-stream + x-sbx-framing: + description: Send ordered metadata/content pairs. Each metadata part is application/json; the following content part is application/octet-stream containing raw bytes, including an empty file. Content-Disposition is form-data with the corresponding part name. Content-Transfer-Encoding is refused. A clean closing multipart delimiter is required; truncated content never completes the current item. Malformed occurrences report invalidArgument with a BadRequest detail identifying the occurrence index and path. + partOrder: + - metadata + - content + repeatedItems: true + requiresClosingDelimiter: true + description: Send ordered metadata/content pairs. Each metadata part is application/json; the following content part is application/octet-stream containing raw bytes, including an empty file. Content-Disposition is form-data with the corresponding part name. Content-Transfer-Encoding is refused. A clean closing multipart delimiter is required; truncated content never completes the current item. Malformed occurrences report invalidArgument with a BadRequest detail identifying the occurrence index and path. + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/FilesUploadResponse" + "409": + description: |- + The path resolves through a symlink that does not resolve to a target inside the sandbox jail. + + The path names an existing socket, FIFO, device, or other non-regular object where a regular file is required. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + path_authority_symlink_escape: + summary: The path resolves through a symlink that does not resolve to a target inside the sandbox jail. + value: + code: failedPrecondition + message: "path_authority_symlink_escape: The path resolves through a symlink that does not resolve to a target inside the sandbox jail." + path_authority_nonregular_object: + summary: The path names an existing socket, FIFO, device, or other non-regular object where a regular file is required. + value: + code: failedPrecondition + message: "path_authority_nonregular_object: The path names an existing socket, FIFO, device, or other non-regular object where a regular file is required." + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: FAILED_PRECONDITION + description: The path resolves through a symlink that does not resolve to a target inside the sandbox jail. + reason: path_authority_symlink_escape + - code: FAILED_PRECONDITION + description: The path names an existing socket, FIFO, device, or other non-regular object where a regular file is required. + reason: path_authority_nonregular_object + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesFilesWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + x-sbx-http-transfer: + request: + mediaType: multipart/form-data + bytesField: data + metadataField: header + repeatedItems: true + itemPathField: header.path + x-sbx-request-schema: + $ref: "#/components/schemas/FilesUploadRequest" + x-sbx-response-schema: + $ref: "#/components/schemas/FilesUploadResponse" + /v1/identity/exchange: + post: + tags: + - Credentials + summary: ExchangeDockerCredential consumes an identity token once for the authenticated owner. + description: |- + ExchangeDockerCredential consumes an identity token once for the authenticated owner. + The exchanged credential is stored for that owner; the response is empty. + + Returns unimplemented when this operation is unavailable. Support does not grant permission. + operationId: exchangeDockerCredential + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/IdentityExchangeServiceExchangeDockerCredentialRequest" + required: true + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ExchangeCompositionCredentialResponse" + "501": + description: This operation is not available on this service. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + unimplemented: + summary: This operation is not available on this service. + value: + code: unimplemented + message: This operation is not available on this service. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: UNIMPLEMENTED + description: This operation is not available on this service. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - credentialsExchange + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/images: + get: + tags: + - Images + summary: ListImages lists images in the caller's owner scope. + description: ListImages lists images in the caller's owner scope. + operationId: listImages + parameters: + - name: pageSize + in: query + description: |- + page_size is an optional page size. + + Omitted or zero uses the backend default. Unless the operation states otherwise, page-size limits and handling of larger requests are backend-specific; use the backend support guide. Continue with nextPageToken until it is empty. + schema: + type: integer + title: page_size + required: false + - name: pageToken + in: query + description: page_token is an opaque continuation token. + schema: + type: string + title: page_token + required: false + - name: filter + in: query + description: |- + filter is comma-separated exact-match field=value terms; the portable fields are + name, display_name, uid and status. + schema: + type: string + title: filter + required: false + - name: orderBy + in: query + description: |- + order_by is a single order field with optional direction; the portable fields are + created_at and name. + schema: + type: string + title: order_by + required: false + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ListImagesResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - imagesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + post: + tags: + - Images + summary: CreateImage registers pushed OCI content or disk content derived from a snapshot or sandbox. + description: CreateImage registers pushed OCI content or disk content derived from a snapshot or sandbox. + operationId: createImage + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateImageRequest" + required: true + responses: + "201": + description: Success + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Image" + - not: + properties: + status: + enum: + - pending + - waitingForPush + - preparing + required: + - status + headers: + Cache-Control: + required: true + schema: + type: string + const: no-store + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "202": + description: Accepted. The resource is still progressing; read it or follow its events until completion. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Image" + - properties: + status: + enum: + - pending + - waitingForPush + - preparing + required: + - status + headers: + Cache-Control: + required: true + schema: + type: string + const: no-store + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "409": + description: |- + Creation conflicts with an existing resource (ALREADY_EXISTS). + + The Idempotency-Key was already used with a different payload. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + failedPrecondition: + summary: The Idempotency-Key was already used with a different payload. + value: + code: failedPrecondition + message: The Idempotency-Key was already used with a different payload. + headers: + Cache-Control: + required: true + schema: + type: string + enum: + - no-store + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + headers: + Cache-Control: + required: true + schema: + type: string + enum: + - no-store + x-sbx-error-responses: + - code: FAILED_PRECONDITION + description: The Idempotency-Key was already used with a different payload. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - imagesWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + x-sbx-resource-response: + $ref: "#/components/schemas/Image" + parameters: + - name: Idempotency-Key + in: header + description: Replay key. Reusing it with a different payload fails with failedPrecondition; accepted keys are retained for at least 24 hours. + schema: + type: string + title: Idempotency-Key + description: Idempotency key. A replay returns the first result; the same key with a different payload fails with FAILED_PRECONDITION; retained at least 24 hours. + /v1/images/{image}: + get: + tags: + - Images + summary: GetImage reads one image by its resource name. + description: GetImage reads one image by its resource name. + operationId: getImage + parameters: + - name: image + in: path + description: The image id. + required: true + schema: + type: string + - name: If-None-Match + in: header + description: A quoted entity-tag or comma-separated list, compared weakly with the current resource etag. A match, including a weak form or wildcard *, answers 304 without a body after authorization. Malformed input is treated as no match. + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Image" + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "304": + description: The resource's etag equals the If-None-Match value, so this response carries no body. + "404": + description: The target is absent or not visible within the caller's scope. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + notFound: + summary: The target is absent or not visible within the caller's scope. + value: + code: notFound + message: The target is absent or not visible within the caller's scope. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: NOT_FOUND + description: The target is absent or not visible within the caller's scope. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - imagesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + delete: + tags: + - Images + summary: DeleteImage deletes an image; deleting an already absent image succeeds. + description: DeleteImage deletes an image; deleting an already absent image succeeds. + operationId: deleteImage + parameters: + - name: image + in: path + description: The image id. + required: true + schema: + type: string + - name: If-Match + in: header + description: "The complete quoted strong entity-tag observed on the resource, including its quotes. Every mutation of an existing resource requires it: an absent value answers 428 and a stale one 412, each carrying failedPrecondition in the body." + required: true + schema: + type: string + title: If-Match + description: |- + etag is the required precondition, the value the target's last read returned. + An absent one refuses etag_required and a stale one etag_mismatch. + responses: + "204": + description: Success. The response carries no body. + "409": + description: |- + The target's preparation, capture, or import has not finished; deletion is refused until it settles to a terminal state. + + An admitted consumer or live resource holds a content lease on the target; deletion is refused while any lease remains held. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + deletion_preparation_incomplete: + summary: The target's preparation, capture, or import has not finished; deletion is refused until it settles to a terminal state. + value: + code: failedPrecondition + message: "deletion_preparation_incomplete: The target's preparation, capture, or import has not finished; deletion is refused until it settles to a terminal state." + deletion_content_lease_held: + summary: An admitted consumer or live resource holds a content lease on the target; deletion is refused while any lease remains held. + value: + code: failedPrecondition + message: "deletion_content_lease_held: An admitted consumer or live resource holds a content lease on the target; deletion is refused while any lease remains held." + "412": + description: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_mismatch: + summary: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + value: + code: failedPrecondition + message: "etag_mismatch: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value." + details: + - "@type": type.googleapis.com/docker.sandboxes.v1.EtagMismatch + currentEtag: v2 + "428": + description: No precondition was sent. The body carries failedPrecondition naming etag_required. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_required: + summary: No precondition was sent. The body carries failedPrecondition naming etag_required. + value: + code: failedPrecondition + message: "etag_required: No precondition was sent. The body carries failedPrecondition naming etag_required." + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: FAILED_PRECONDITION + description: The target's preparation, capture, or import has not finished; deletion is refused until it settles to a terminal state. + reason: deletion_preparation_incomplete + - code: FAILED_PRECONDITION + description: An admitted consumer or live resource holds a content lease on the target; deletion is refused while any lease remains held. + reason: deletion_content_lease_held + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - imagesWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/images/{image}/pull-spec: + get: + tags: + - Images + summary: GetImagePullSpec returns short-lived pull material through registry-transfer support. + description: |- + GetImagePullSpec returns short-lived pull material through registry-transfer support. + Action: the pull spec is derived material about the image, not a sub-resource of it. + + Returns unimplemented when this operation is unavailable. Support does not grant permission. + operationId: getImagePullSpec + parameters: + - name: image + in: path + description: The image id. + required: true + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ImagePullSpec" + headers: + Cache-Control: + required: true + schema: + type: string + const: no-store + "501": + description: This operation is not available on this service. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + unimplemented: + summary: This operation is not available on this service. + value: + code: unimplemented + message: This operation is not available on this service. + headers: + Cache-Control: + required: true + schema: + type: string + enum: + - no-store + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + headers: + Cache-Control: + required: true + schema: + type: string + enum: + - no-store + x-sbx-error-responses: + - code: UNIMPLEMENTED + description: This operation is not available on this service. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - imagesPull + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/mcp-servers/{mcpServer}/authorization: + get: + tags: + - MCP gateways + summary: GetMcpAuthorization reads the owner-scoped flow, including its pending URL, under mcp.write. + description: GetMcpAuthorization reads the owner-scoped flow, including its pending URL, under mcp.write. + operationId: getMcpAuthorization + parameters: + - name: mcpServer + in: path + description: The mcpServer id. + required: true + schema: + type: string + - name: If-None-Match + in: header + description: A quoted entity-tag or comma-separated list, compared weakly with the current resource etag. A match, including a weak form or wildcard *, answers 304 without a body after authorization. Malformed input is treated as no match. + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/McpAuthorization" + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "304": + description: The resource's etag equals the If-None-Match value, so this response carries no body. + "404": + description: The target is absent or not visible within the caller's scope. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + notFound: + summary: The target is absent or not visible within the caller's scope. + value: + code: notFound + message: The target is absent or not visible within the caller's scope. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: NOT_FOUND + description: The target is absent or not visible within the caller's scope. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - mcpWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/mcp-servers/{mcpServer}/authorization/authorize: + post: + tags: + - MCP gateways + summary: AuthorizeMcpServer authorizes credentials for one named upstream. + description: |- + AuthorizeMcpServer authorizes credentials for one named upstream. + Action: authorization runs an interactive grant whose result is not the request body. + operationId: authorizeMcpServer + parameters: + - name: mcpServer + in: path + description: The mcpServer id. + required: true + schema: + type: string + - name: If-Match + in: header + description: Optional current authorization etag. A supplied stale value answers 412 with failedPrecondition; omission permits create or convergence, including forced reauthorization. + schema: + type: string + title: If-Match + description: (OPTIONAL) etag optionally pins the current authorization; a supplied stale value refuses. + requestBody: + content: + application/json: + schema: + type: object + properties: + remoteUrl: + type: + - string + - "null" + format: uri + description: remote_url binds authorization to a concrete upstream URL when supplied. + forceReauth: + type: boolean + description: |- + force_reauth starts a fresh flow while existing credentials serve until completion. + Repeating a pending flow converges without reminting. + title: AuthorizeMcpServerRequest + additionalProperties: false + description: AuthorizeMcpServerRequest starts or refreshes MCP upstream authorization. + required: true + responses: + "200": + description: Success + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/McpAuthorization" + - not: + properties: + status: + enum: + - pending + required: + - status + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "202": + description: Accepted. The resource is still progressing; read it or follow its events until completion. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/McpAuthorization" + - properties: + status: + enum: + - pending + required: + - status + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "412": + description: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_mismatch: + summary: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + value: + code: failedPrecondition + message: "etag_mismatch: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value." + details: + - "@type": type.googleapis.com/docker.sandboxes.v1.EtagMismatch + currentEtag: v2 + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - mcpWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + x-sbx-resource-response: + $ref: "#/components/schemas/McpAuthorization" + /v1/network-policies: + get: + tags: + - Network policies + summary: GetNetworkPolicies returns effective and exact views of the same installed generation. + description: GetNetworkPolicies returns effective and exact views of the same installed generation. + operationId: getNetworkPoliciesInOwnerScope + parameters: [] + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/GetNetworkPoliciesResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - networkPoliciesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/policy-logs: + get: + tags: + - Network policies + summary: ListPolicyLogEntries lists observed policy decisions. + description: ListPolicyLogEntries lists observed policy decisions. + operationId: listPolicyLogEntriesInOwnerScope + parameters: + - name: pageSize + in: query + description: |- + page_size is an optional page size. + + Omitted or zero uses the backend default. Unless the operation states otherwise, page-size limits and handling of larger requests are backend-specific; use the backend support guide. Continue with nextPageToken until it is empty. + schema: + type: integer + title: page_size + required: false + - name: pageToken + in: query + description: page_token is an opaque continuation token. + schema: + type: string + title: page_token + required: false + - name: filter + in: query + description: |- + filter is comma-separated exact-match field=value terms; the portable field is + domain. + schema: + type: string + title: filter + required: false + - name: since + in: query + description: An RFC 3339 timestamp, for example 2026-01-01T12:00:00Z. Fractional seconds may contain up to nine digits. The supported range is 0001-01-01T00:00:00Z through 9999-12-31T23:59:59.999999999Z. + schema: + type: string + examples: + - 2023-01-15T01:30:15.01Z + - 2024-12-25T12:00:00Z + format: date-time + required: false + - name: until + in: query + description: An RFC 3339 timestamp, for example 2026-01-01T12:00:00Z. Fractional seconds may contain up to nine digits. The supported range is 0001-01-01T00:00:00Z through 9999-12-31T23:59:59.999999999Z. + schema: + type: string + examples: + - 2023-01-15T01:30:15.01Z + - 2024-12-25T12:00:00Z + format: date-time + required: false + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ListPolicyLogEntriesResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - networkPoliciesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/processes: + get: + tags: + - Processes + summary: ListProcesses lists processes in creation order. + description: ListProcesses lists processes in creation order. + operationId: listProcesses + parameters: + - name: pageSize + in: query + description: |- + page_size is an optional page size. + + Omitted or zero uses the backend default. Unless the operation states otherwise, page-size limits and handling of larger requests are backend-specific; use the backend support guide. Continue with nextPageToken until it is empty. + schema: + type: integer + title: page_size + required: false + - name: pageToken + in: query + description: page_token is an optional continuation token. + schema: + type: string + title: page_token + required: false + - name: filter + in: query + description: |- + filter is comma-separated exact-match field=value terms; the portable fields are + session and state. + schema: + type: string + title: filter + required: false + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ListProcessesResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesExec + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + post: + tags: + - Processes + summary: CreateProcess starts a durable interactive process and returns its resource name. + description: CreateProcess starts a durable interactive process and returns its resource name. + operationId: createProcess + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateProcessRequest" + required: true + responses: + "201": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Process" + "409": + description: |- + Creation conflicts with an existing resource (ALREADY_EXISTS). + + The Idempotency-Key was already used with a different payload. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + failedPrecondition: + summary: The Idempotency-Key was already used with a different payload. + value: + code: failedPrecondition + message: The Idempotency-Key was already used with a different payload. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: FAILED_PRECONDITION + description: The Idempotency-Key was already used with a different payload. + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesExec + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + parameters: + - name: Idempotency-Key + in: header + description: Replay key. Reusing it with a different payload fails with failedPrecondition; accepted keys are retained for at least 24 hours. + schema: + type: string + title: Idempotency-Key + description: Idempotency key. A replay returns the first result; the same key with a different payload fails with FAILED_PRECONDITION; retained at least 24 hours. + /v1/processes/exec: + post: + tags: + - Processes + summary: Exec runs one command to completion. + description: |- + Exec runs one command to completion. A retry after transport failure may run it again. + Action at the collection root: it runs a command to completion instead of creating a + process a caller can address. + operationId: exec + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ExecRequest" + required: true + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ExecResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesExec + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/processes/{process}: + get: + tags: + - Processes + summary: GetProcess reads one process by its endpoint-scoped resource name. + description: GetProcess reads one process by its endpoint-scoped resource name. + operationId: getProcess + parameters: + - name: process + in: path + description: The process id. + required: true + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Process" + "404": + description: The target is absent or not visible within the caller's scope. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + notFound: + summary: The target is absent or not visible within the caller's scope. + value: + code: notFound + message: The target is absent or not visible within the caller's scope. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: NOT_FOUND + description: The target is absent or not visible within the caller's scope. + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesExec + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/processes/{process}/interact: + get: + tags: + - Processes + summary: Interact binds the complete process name from the path to the first Attach frame. + description: |- + Interact binds the complete process name from the path to the first Attach frame. + A conflicting name is INVALID_ARGUMENT before any attach. + operationId: interact + parameters: + - name: process + in: path + description: The process id. + required: true + schema: + type: string + responses: + "101": + description: "The connection becomes a sandboxes.v1 session: one InteractRequest per inbound text frame and one ProcessOutput per outbound one. Path parameters identify the target; the first attach frame must agree. Refusals after upgrade carry an error frame followed by a close code." + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-websocket: + subprotocol: sandboxes.v1 + firstRequestField: attach + resumeRequestField: attach.resumeFrom + resumeResponseField: chunk.streamSequence + bearerSubprotocolPrefix: sandboxes.bearer.v1. + maxBearerTokenBytes: 4096 + requestFrame: + $ref: "#/components/schemas/InteractRequest" + responseFrame: + $ref: "#/components/schemas/ProcessOutput" + terminalFrame: The last text frame before a close carries the one error body, because a close reason holds at most 123 bytes. + errorFrame: + $ref: "#/components/schemas/Error" + authentication: Use exactly one sandbox-scoped Authorization bearer credential or, when endpoint credentialTransports advertises webSocketSubprotocol, offer sandboxes.bearer.v1. followed by canonical unpadded base64url of that credential alongside sandboxes.v1. The decoded token is limited to 4096 bytes. The server echoes only sandboxes.v1; mixed or duplicate credentials, query credentials and cookie-only authentication are refused. + browser: Open a WSS endpoint using its scoped credential. Browsers may send cookies; the server ignores them as authority and permits cross-origin explicit bearer authentication. Browser WebSocket errors do not expose pre-upgrade HTTP status or bodies; only post-upgrade Error frames are typed. No reconnect or input replay is automatic. + reconnect: Acquire a fresh scoped credential, open a new connection, and send attach with attach.resumeFrom set to the last received chunk.streamSequence. Resume replays output only; never replay stdin or process creation. + preUpgradeStatus: + "400": The request has an invalid subprotocol offer, mixed or malformed credentials, an oversized credential, or a query parameter. + "401": The scoped credential is missing, invalid or expired. + "403": The scoped credential does not grant this endpoint permission. + "404": The scoped credential does not address this sandbox endpoint. + closeCode: + "1000": The service ended the stream, or the caller cancelled it. + "1002": The handshake negotiated no subprotocol. + "1003": A frame arrived as binary; every frame is text. + "1007": A frame was not the public JSON request message. + "1008": The service refused with a 4xx error, or the endpoint credential expired. + "1009": A frame exceeded the route’s frame limit. + "1011": The service failed with a code the front door maps to a 5xx. + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + - sandboxWebSocketBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesExec + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + x-sbx-request-schema: + $ref: "#/components/schemas/InteractRequest" + x-sbx-response-schema: + $ref: "#/components/schemas/ProcessOutput" + /v1/processes/{process}/output: + get: + tags: + - Processes + summary: ReadOutput returns one page of retained output after a stream_sequence, the same cursor Interact resumes from, so a REST client can read what a process printed. + description: |- + ReadOutput returns one page of retained output after a stream_sequence, the + same cursor Interact resumes from, so a REST client can read what a process printed. + operationId: readOutput + parameters: + - name: process + in: path + description: The process id. + required: true + schema: + type: string + - name: resumeFrom + in: query + description: |- + resume_from returns output after this stream_sequence; 0 means from the start. + Missing output after this point fails with FAILED_PRECONDITION, as for Attach. + schema: + type: + - integer + - string + title: resume_from + format: int64 + required: false + - name: maxBytes + in: query + description: |- + max_bytes is a soft bound on the page's data: whole chunks only, and the first retained + chunk always fits so a caller always advances. 0 means the backend's default page. + schema: + type: integer + title: max_bytes + required: false + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ReadOutputResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesExec + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/processes/{process}/signal: + post: + tags: + - Processes + summary: Signal sends one signal to an existing process. + description: |- + Signal sends one signal to an existing process. + Action: a signal is delivered to a running process and leaves no representation to + replace. + operationId: signal + parameters: + - name: process + in: path + description: The process id. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + signal: + not: + enum: + - unspecified + description: signal is the signal to send. + $ref: "#/components/schemas/ProcessSignal" + title: SignalRequest + required: + - signal + additionalProperties: false + description: SignalRequest sends one signal. + required: true + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/SignalResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + servers: + - url: "{endpoint}" + variables: + endpoint: + default: https://sandbox.invalid + description: The sandbox API base URL from Sandbox.core.endpoint.uri; preserve its path prefix when appending /v1 routes. HTTP endpoints require a scoped endpoint bearer; direct Unix sockets use socket access control. + security: + - sandboxBearer: [] + x-sbx-serving-surface: sandboxEndpoint + x-sbx-plane: data + x-sbx-required-permissions: + - sandboxesExec + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/sandboxes: + get: + tags: + - Sandboxes + summary: ListSandboxes lists sandboxes in the caller's owner scope. + description: ListSandboxes lists sandboxes in the caller's owner scope. + operationId: listSandboxes + parameters: + - name: pageSize + in: query + description: |- + page_size is an optional page size. + + Omitted or zero uses the backend default. Unless the operation states otherwise, page-size limits and handling of larger requests are backend-specific; use the backend support guide. Continue with nextPageToken until it is empty. + schema: + type: integer + title: page_size + required: false + - name: pageToken + in: query + description: page_token is an opaque continuation token. + schema: + type: string + title: page_token + required: false + - name: filter + in: query + description: |- + filter is comma-separated exact-match field=value terms; the portable fields are + name, display_name, uid, status and agent. + schema: + type: string + title: filter + required: false + - name: orderBy + in: query + description: |- + order_by is a single order field with optional direction; the portable fields are + created_at and name. + schema: + type: string + title: order_by + required: false + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ListSandboxesResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - sandboxesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + post: + tags: + - Sandboxes + summary: CreateSandbox admits a sandbox. + description: |- + CreateSandbox admits a sandbox. Unknown fields at any level are rejected before + replay lookup, reservation, policy binding, or workload effects. + operationId: createSandbox + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateSandboxRequest" + required: true + responses: + "201": + description: Success + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Sandbox" + - not: + properties: + core: + properties: + status: + enum: + - creating + - starting + required: + - status + required: + - core + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "202": + description: Accepted. The resource is still progressing; read it or follow its events until completion. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Sandbox" + - properties: + core: + properties: + status: + enum: + - creating + - starting + required: + - status + required: + - core + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "409": + description: |- + Creation conflicts with an existing resource (ALREADY_EXISTS). + + Kit admission was refused; an available settled report describes the rejected fields. + + The Idempotency-Key was already used with a different payload. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + failedPrecondition: + summary: The Idempotency-Key was already used with a different payload. + value: + code: failedPrecondition + message: The Idempotency-Key was already used with a different payload. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: FAILED_PRECONDITION + description: Kit admission was refused; an available settled report describes the rejected fields. + detailType: docker.sandboxes.v1.KitFieldOutcomeReport + - code: FAILED_PRECONDITION + description: The Idempotency-Key was already used with a different payload. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - sandboxesCreate + x-sbx-conditional-permissions: + - permission: mcpWrite + requestFieldPresent: mcp + x-sbx-authenticated-only: false + x-sbx-resource-response: + $ref: "#/components/schemas/Sandbox" + parameters: + - name: Idempotency-Key + in: header + description: Replay key. Reusing it with a different payload fails with failedPrecondition; accepted keys are retained for at least 24 hours. + schema: + type: string + title: Idempotency-Key + description: Idempotency key. A replay returns the first result; the same key with a different payload fails with FAILED_PRECONDITION; retained at least 24 hours. + x-sbx-error-details: + KitFieldOutcomeReport: + $ref: "#/components/schemas/KitFieldOutcomeReport" + /v1/sandboxes/{sandbox}: + get: + tags: + - Sandboxes + summary: GetSandbox reads one sandbox by its resource name. + description: GetSandbox reads one sandbox by its resource name. + operationId: getSandbox + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + - name: If-None-Match + in: header + description: A quoted entity-tag or comma-separated list, compared weakly with the current resource etag. A match, including a weak form or wildcard *, answers 304 without a body after authorization. Malformed input is treated as no match. + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Sandbox" + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "304": + description: The resource's etag equals the If-None-Match value, so this response carries no body. + "404": + description: The target is absent or not visible within the caller's scope. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + notFound: + summary: The target is absent or not visible within the caller's scope. + value: + code: notFound + message: The target is absent or not visible within the caller's scope. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: NOT_FOUND + description: The target is absent or not visible within the caller's scope. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - sandboxesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + delete: + tags: + - Sandboxes + summary: DeleteSandbox returns the deleting resource while work remains, or an empty message when absent. + description: DeleteSandbox returns the deleting resource while work remains, or an empty message when absent. + operationId: deleteSandbox + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + - name: Idempotency-Key + in: header + description: Replay key. Reusing it with a different payload fails with failedPrecondition; accepted keys are retained for at least 24 hours. + schema: + type: string + title: Idempotency-Key + description: Idempotency key. A replay returns the first result; the same key with a different payload fails with FAILED_PRECONDITION; retained at least 24 hours. + - name: If-Match + in: header + description: "The complete quoted strong entity-tag observed on the resource, including its quotes. Every mutation of an existing resource requires it: an absent value answers 428 and a stale one 412, each carrying failedPrecondition in the body." + required: true + schema: + type: string + title: If-Match + description: |- + etag is the required precondition, the value the target's last read returned. + An absent one refuses etag_required and a stale one etag_mismatch. + - name: force + in: query + description: force requests deletion even when the backend would otherwise refuse the state. + schema: + type: boolean + title: force + required: false + responses: + "202": + description: Accepted. The resource is still progressing; read it or follow its events until completion. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Sandbox" + - properties: + core: + properties: + status: + enum: + - deleting + required: + - status + required: + - core + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "204": + description: Success. The response carries no body. + "409": + description: The Idempotency-Key was already used with a different payload. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + failedPrecondition: + summary: The Idempotency-Key was already used with a different payload. + value: + code: failedPrecondition + message: The Idempotency-Key was already used with a different payload. + "412": + description: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_mismatch: + summary: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + value: + code: failedPrecondition + message: "etag_mismatch: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value." + details: + - "@type": type.googleapis.com/docker.sandboxes.v1.EtagMismatch + currentEtag: v2 + "428": + description: No precondition was sent. The body carries failedPrecondition naming etag_required. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_required: + summary: No precondition was sent. The body carries failedPrecondition naming etag_required. + value: + code: failedPrecondition + message: "etag_required: No precondition was sent. The body carries failedPrecondition naming etag_required." + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: FAILED_PRECONDITION + description: The Idempotency-Key was already used with a different payload. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - sandboxesDelete + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + x-sbx-resource-response: + $ref: "#/components/schemas/Sandbox" + patch: + tags: + - Sandboxes + summary: UpdateSandbox changes only the display label under the observed etag. + description: |- + UpdateSandbox changes only the display label under the observed etag. + Its resource name, backing identity and child addresses remain unchanged. + operationId: updateSandbox + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + - name: Idempotency-Key + in: header + description: Replay key. Reusing it with a different payload fails with failedPrecondition; accepted keys are retained for at least 24 hours. + required: true + schema: + type: string + title: Idempotency-Key + minLength: 1 + description: Idempotency key. A replay returns the first result; the same key with a different payload fails with FAILED_PRECONDITION; retained at least 24 hours. + - name: If-Match + in: header + description: "The complete quoted strong entity-tag observed on the resource, including its quotes. Every mutation of an existing resource requires it: an absent value answers 428 and a stale one 412, each carrying failedPrecondition in the body." + required: true + schema: + type: string + title: If-Match + description: |- + etag is the required precondition, the value the target's last read returned. + An absent one refuses etag_required and a stale one etag_mismatch. + requestBody: + content: + application/json: + schema: + type: object + properties: + displayName: + type: + - string + - "null" + maxLength: 64 + minLength: 1 + pattern: ^[a-zA-Z0-9_-]+$ + description: display_name must be present and retains the existing scoped label rules. + title: UpdateSandboxRequest + required: + - displayName + additionalProperties: false + description: UpdateSandboxRequest changes only the display label under the observed etag. + required: true + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Sandbox" + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "409": + description: The Idempotency-Key was already used with a different payload. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + failedPrecondition: + summary: The Idempotency-Key was already used with a different payload. + value: + code: failedPrecondition + message: The Idempotency-Key was already used with a different payload. + "412": + description: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_mismatch: + summary: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + value: + code: failedPrecondition + message: "etag_mismatch: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value." + details: + - "@type": type.googleapis.com/docker.sandboxes.v1.EtagMismatch + currentEtag: v2 + "428": + description: No precondition was sent. The body carries failedPrecondition naming etag_required. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_required: + summary: No precondition was sent. The body carries failedPrecondition naming etag_required. + value: + code: failedPrecondition + message: "etag_required: No precondition was sent. The body carries failedPrecondition naming etag_required." + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: FAILED_PRECONDITION + description: The Idempotency-Key was already used with a different payload. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - sandboxesRename + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/sandboxes/{sandbox}/endpoint-credentials: + post: + tags: + - Sandboxes + summary: CreateEndpointCredential grants short-lived access to one sandbox endpoint. + description: |- + CreateEndpointCredential grants short-lived access to one sandbox endpoint. + Issuance checks current authority and never stores a token in the replay ledger. + operationId: createEndpointCredential + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + permissions: + type: array + items: + $ref: "#/components/schemas/Permission" + enum: + - sandboxesExec + - sandboxesFilesRead + - sandboxesFilesWrite + minItems: 1 + uniqueItems: true + description: "Allowed values: sandboxesExec, sandboxesFilesRead, sandboxesFilesWrite." + ttl: + description: ttl defaults to five minutes; supplied durations are clamped to one second through five minutes. + $ref: "#/components/schemas/Duration" + title: CreateEndpointCredentialRequest + required: + - permissions + additionalProperties: false + description: CreateEndpointCredentialRequest requests only endpoint permissions the caller holds. + required: true + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/EndpointCredential" + headers: + Cache-Control: + required: true + schema: + type: string + const: no-store + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + headers: + Cache-Control: + required: true + schema: + type: string + enum: + - no-store + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - sandboxesCredential + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/sandboxes/{sandbox}/mcp-gateway: + get: + tags: + - MCP gateways + summary: GetMcpGateway reads gateway state for the sandbox. + description: GetMcpGateway reads gateway state for the sandbox. + operationId: getMcpGateway + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/McpGateway" + "404": + description: The target is absent or not visible within the caller's scope. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + notFound: + summary: The target is absent or not visible within the caller's scope. + value: + code: notFound + message: The target is absent or not visible within the caller's scope. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: NOT_FOUND + description: The target is absent or not visible within the caller's scope. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - mcpRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/sandboxes/{sandbox}/mcp-gateway/servers: + post: + tags: + - MCP gateways + summary: AddMcpGatewayServer adds one server to an existing ready gateway. + description: AddMcpGatewayServer adds one server to an existing ready gateway. + operationId: addMcpGatewayServer + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + server: + type: string + minLength: 1 + description: |- + server is the external configured key; a supplied registration must define this same key. + A mismatch refuses FAILED_PRECONDITION before changing the gateway. + title: AddMcpGatewayServerRequest + required: + - server + additionalProperties: false + description: AddMcpGatewayServerRequest adds one server to an existing ready gateway. + required: true + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AddMcpGatewayServerResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - mcpWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/sandboxes/{sandbox}/mcp-gateway/start: + post: + tags: + - MCP gateways + summary: StartMcpGateway ensures a gateway exists for the sandbox. + description: |- + StartMcpGateway ensures a gateway exists for the sandbox. + Action: a lifecycle transition is not a representation a caller can PUT. + operationId: startMcpGateway + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + servers: + type: array + items: + type: string + minLength: 1 + description: servers are initial server names for a backend-minted gateway. + static: + type: boolean + description: static pins the requested server set and closes gateway-side discovery. + gatewayUrl: + type: + - string + - "null" + format: uri + description: gateway_url attaches read-only to a pre-existing shareable gateway when supported. + title: StartMcpGatewayRequest + additionalProperties: false + description: |- + StartMcpGatewayRequest ensures a sandbox gateway exists. + servers must be empty when gateway_url is set + required: true + responses: + "200": + description: Success + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/McpGateway" + - not: + properties: + state: + enum: + - provisioning + required: + - state + "202": + description: Accepted. The resource is still progressing; read it or follow its events until completion. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/McpGateway" + - properties: + state: + enum: + - provisioning + required: + - state + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - mcpWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + x-sbx-resource-response: + $ref: "#/components/schemas/McpGateway" + /v1/sandboxes/{sandbox}/mcp-gateway/stop: + post: + tags: + - MCP gateways + summary: StopMcpGateway stops or detaches the gateway and succeeds when already absent. + description: |- + StopMcpGateway stops or detaches the gateway and succeeds when already absent. + Action: a lifecycle transition is not a representation a caller can PUT. + operationId: stopMcpGateway + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/StopMcpGatewayResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - mcpWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/sandboxes/{sandbox}/network-policies: + get: + tags: + - Network policies + summary: GetNetworkPolicies returns effective and exact views of the same installed generation. + description: GetNetworkPolicies returns effective and exact views of the same installed generation. + operationId: getNetworkPolicies + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/GetNetworkPoliciesResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - networkPoliciesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/sandboxes/{sandbox}/policy-logs: + get: + tags: + - Network policies + summary: ListPolicyLogEntries lists observed policy decisions. + description: ListPolicyLogEntries lists observed policy decisions. + operationId: listPolicyLogEntries + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + - name: pageSize + in: query + description: |- + page_size is an optional page size. + + Omitted or zero uses the backend default. Unless the operation states otherwise, page-size limits and handling of larger requests are backend-specific; use the backend support guide. Continue with nextPageToken until it is empty. + schema: + type: integer + title: page_size + required: false + - name: pageToken + in: query + description: page_token is an opaque continuation token. + schema: + type: string + title: page_token + required: false + - name: filter + in: query + description: |- + filter is comma-separated exact-match field=value terms; the portable field is + domain. + schema: + type: string + title: filter + required: false + - name: since + in: query + description: An RFC 3339 timestamp, for example 2026-01-01T12:00:00Z. Fractional seconds may contain up to nine digits. The supported range is 0001-01-01T00:00:00Z through 9999-12-31T23:59:59.999999999Z. + schema: + type: string + examples: + - 2023-01-15T01:30:15.01Z + - 2024-12-25T12:00:00Z + format: date-time + required: false + - name: until + in: query + description: An RFC 3339 timestamp, for example 2026-01-01T12:00:00Z. Fractional seconds may contain up to nine digits. The supported range is 0001-01-01T00:00:00Z through 9999-12-31T23:59:59.999999999Z. + schema: + type: string + examples: + - 2023-01-15T01:30:15.01Z + - 2024-12-25T12:00:00Z + format: date-time + required: false + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ListPolicyLogEntriesResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - networkPoliciesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/sandboxes/{sandbox}/ports: + get: + tags: + - Sandboxes + summary: ListPorts lists currently published ports for one sandbox. + description: ListPorts lists currently published ports for one sandbox. + operationId: listPorts + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ListPortsResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - sandboxesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + post: + tags: + - Sandboxes + summary: CreatePort makes one sandbox port reachable outside the sandbox. + description: CreatePort makes one sandbox port reachable outside the sandbox. + operationId: createPort + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + - name: Idempotency-Key + in: header + description: Replay key. Reusing it with a different payload fails with failedPrecondition; accepted keys are retained for at least 24 hours. + schema: + type: string + title: Idempotency-Key + description: Idempotency key. A replay returns the first result; the same key with a different payload fails with FAILED_PRECONDITION; retained at least 24 hours. + requestBody: + content: + application/json: + schema: + type: object + properties: + port: + description: port is the requested publication. + $ref: "#/components/schemas/CreatePortRequestPortInput" + title: CreatePortRequest + required: + - port + additionalProperties: false + description: CreatePortRequest publishes one sandbox port. + required: true + responses: + "201": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Port" + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "409": + description: |- + Creation conflicts with an existing resource (ALREADY_EXISTS). + + The sandbox already publishes this port number. + + The Idempotency-Key was already used with a different payload. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + alreadyExists: + summary: The sandbox already publishes this port number. + value: + code: alreadyExists + message: The sandbox already publishes this port number. + failedPrecondition: + summary: The Idempotency-Key was already used with a different payload. + value: + code: failedPrecondition + message: The Idempotency-Key was already used with a different payload. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: ALREADY_EXISTS + description: The sandbox already publishes this port number. + - code: FAILED_PRECONDITION + description: The Idempotency-Key was already used with a different payload. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - sandboxesPorts + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/sandboxes/{sandbox}/ports/{port}: + get: + tags: + - Sandboxes + summary: GetPort reads one published port by its resource name. + description: GetPort reads one published port by its resource name. + operationId: getPort + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + - name: port + in: path + description: The port id. + required: true + schema: + type: string + - name: If-None-Match + in: header + description: A quoted entity-tag or comma-separated list, compared weakly with the current resource etag. A match, including a weak form or wildcard *, answers 304 without a body after authorization. Malformed input is treated as no match. + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Port" + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "304": + description: The resource's etag equals the If-None-Match value, so this response carries no body. + "404": + description: No publication exists for this port resource name. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + notFound: + summary: No publication exists for this port resource name. + value: + code: notFound + message: No publication exists for this port resource name. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: NOT_FOUND + description: No publication exists for this port resource name. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - sandboxesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + delete: + tags: + - Sandboxes + summary: DeletePort withdraws one published port. + description: |- + DeletePort withdraws one published port. A name that is not published + succeeds, so a retried delete reports the same result as the first. + operationId: deletePort + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + - name: port + in: path + description: The port id. + required: true + schema: + type: string + - name: If-Match + in: header + description: "The complete quoted strong entity-tag observed on the resource, including its quotes. Every mutation of an existing resource requires it: an absent value answers 428 and a stale one 412, each carrying failedPrecondition in the body." + required: true + schema: + type: string + title: If-Match + description: etag identifies the publication version the caller intends to delete. + responses: + "204": + description: Success. The response carries no body. + "412": + description: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_mismatch: + summary: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + value: + code: failedPrecondition + message: "etag_mismatch: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value." + details: + - "@type": type.googleapis.com/docker.sandboxes.v1.EtagMismatch + currentEtag: v2 + "428": + description: No precondition was sent. The body carries failedPrecondition naming etag_required. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_required: + summary: No precondition was sent. The body carries failedPrecondition naming etag_required. + value: + code: failedPrecondition + message: "etag_required: No precondition was sent. The body carries failedPrecondition naming etag_required." + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - sandboxesPorts + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/sandboxes/{sandbox}/renew-timeout: + post: + tags: + - Sandboxes + summary: RenewSandboxTimeout sets a TTL when timeout renewal is supported. + description: |- + RenewSandboxTimeout sets a TTL when timeout renewal is supported. The TTL starts at acceptance, and the response returns the new expires_at with the sandbox's timeout configuration preserved. + Action: the TTL restarts at acceptance, so the result is not a value the caller could + PUT. + + Returns unimplemented when this operation is unavailable. Support does not grant permission. + operationId: renewSandboxTimeout + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + timeout: + description: timeout is the new TTL relative to server acceptance time. + $ref: "#/components/schemas/Duration" + title: RenewSandboxTimeoutRequest + required: + - timeout + additionalProperties: false + description: RenewSandboxTimeoutRequest sets a new remaining TTL. + required: true + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Sandbox" + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "501": + description: This operation is not available on this service. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + unimplemented: + summary: This operation is not available on this service. + value: + code: unimplemented + message: This operation is not available on this service. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: UNIMPLEMENTED + description: This operation is not available on this service. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - sandboxesLifecycle + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/sandboxes/{sandbox}/snapshots: + post: + tags: + - Snapshots + summary: CreateSnapshot checkpoints a running sandbox; a stopped source is rejected. + description: CreateSnapshot checkpoints a running sandbox; a stopped source is rejected. + operationId: createSnapshot + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + - name: Idempotency-Key + in: header + description: Replay key. Reusing it with a different payload fails with failedPrecondition; accepted keys are retained for at least 24 hours. + schema: + type: string + title: Idempotency-Key + description: Idempotency key. A replay returns the first result; the same key with a different payload fails with FAILED_PRECONDITION; retained at least 24 hours. + requestBody: + content: + application/json: + schema: + type: object + properties: + displayName: + type: string + not: + type: string + enum: + - . + - .. + minLength: 1 + pattern: ^[^/]+$ + description: display_name is a scoped label; the backend assigns the immutable resource ID. + description: + type: string + description: description is caller-supplied text. + captureMode: + description: |- + capture_mode selects disk-only or memory plus disk capture. A value this + enum does not define is rejected, never coerced to a capture. + $ref: "#/components/schemas/CaptureMode" + title: CreateSnapshotRequest + required: + - displayName + additionalProperties: false + description: CreateSnapshotRequest captures one running sandbox. + required: true + responses: + "201": + description: Success + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Snapshot" + - not: + properties: + status: + enum: + - creating + required: + - status + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "202": + description: Accepted. The resource is still progressing; read it or follow its events until completion. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Snapshot" + - properties: + status: + enum: + - creating + required: + - status + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "409": + description: |- + Creation conflicts with an existing resource (ALREADY_EXISTS). + + The Idempotency-Key was already used with a different payload. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + failedPrecondition: + summary: The Idempotency-Key was already used with a different payload. + value: + code: failedPrecondition + message: The Idempotency-Key was already used with a different payload. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: FAILED_PRECONDITION + description: The Idempotency-Key was already used with a different payload. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - snapshotsWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + x-sbx-resource-response: + $ref: "#/components/schemas/Snapshot" + /v1/sandboxes/{sandbox}/ssh-certificates: + post: + tags: + - Sandboxes + summary: IssueSSHCert signs a caller-held public key for sandbox SSH access. + description: |- + IssueSSHCert signs a caller-held public key for sandbox SSH access. + A create: each call mints one certificate under the sandbox's certificate collection. + + Returns unimplemented when this operation is unavailable. Support does not grant permission. + operationId: issueSSHCert + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + publicKey: + type: string + minLength: 1 + description: public_key is the caller-held SSH public key. + ttl: + description: ttl is the requested certificate lifetime. + $ref: "#/components/schemas/Duration" + title: IssueSSHCertRequest + required: + - publicKey + additionalProperties: false + description: IssueSSHCertRequest asks the backend to sign a caller-held public key. + required: true + responses: + "201": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/IssueSSHCertResponse" + headers: + Cache-Control: + required: true + schema: + type: string + const: no-store + "409": + description: Creation conflicts with an existing resource (ALREADY_EXISTS). + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + headers: + Cache-Control: + required: true + schema: + type: string + enum: + - no-store + "501": + description: This operation is not available on this service. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + unimplemented: + summary: This operation is not available on this service. + value: + code: unimplemented + message: This operation is not available on this service. + headers: + Cache-Control: + required: true + schema: + type: string + enum: + - no-store + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + headers: + Cache-Control: + required: true + schema: + type: string + enum: + - no-store + x-sbx-error-responses: + - code: UNIMPLEMENTED + description: This operation is not available on this service. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - sandboxesSsh + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/sandboxes/{sandbox}/start: + post: + tags: + - Sandboxes + summary: StartSandbox starts a stopped sandbox; a backend that preserved memory resumes it. + description: |- + StartSandbox starts a stopped sandbox; a backend that preserved memory resumes it. + Action: a lifecycle transition is not a representation a caller can PUT. + operationId: startSandbox + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + - name: Idempotency-Key + in: header + description: Replay key. Reusing it with a different payload fails with failedPrecondition; accepted keys are retained for at least 24 hours. + schema: + type: string + title: Idempotency-Key + description: Idempotency key. A replay returns the first result; the same key with a different payload fails with FAILED_PRECONDITION; retained at least 24 hours. + - name: If-Match + in: header + description: "The complete quoted strong entity-tag observed on the resource, including its quotes. Every mutation of an existing resource requires it: an absent value answers 428 and a stale one 412, each carrying failedPrecondition in the body." + required: true + schema: + type: string + title: If-Match + description: |- + etag is the required precondition, the value the target's last read returned. + An absent one refuses etag_required and a stale one etag_mismatch. + requestBody: + content: + application/json: + schema: + type: object + properties: {} + title: StartSandboxRequest + additionalProperties: false + description: StartSandboxRequest starts or resumes a sandbox. + required: false + responses: + "200": + description: Success + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Sandbox" + - not: + properties: + core: + properties: + status: + enum: + - starting + required: + - status + required: + - core + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "202": + description: Accepted. The resource is still progressing; read it or follow its events until completion. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Sandbox" + - properties: + core: + properties: + status: + enum: + - starting + required: + - status + required: + - core + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "409": + description: The Idempotency-Key was already used with a different payload. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + failedPrecondition: + summary: The Idempotency-Key was already used with a different payload. + value: + code: failedPrecondition + message: The Idempotency-Key was already used with a different payload. + "412": + description: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_mismatch: + summary: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + value: + code: failedPrecondition + message: "etag_mismatch: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value." + details: + - "@type": type.googleapis.com/docker.sandboxes.v1.EtagMismatch + currentEtag: v2 + "428": + description: No precondition was sent. The body carries failedPrecondition naming etag_required. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_required: + summary: No precondition was sent. The body carries failedPrecondition naming etag_required. + value: + code: failedPrecondition + message: "etag_required: No precondition was sent. The body carries failedPrecondition naming etag_required." + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: FAILED_PRECONDITION + description: The Idempotency-Key was already used with a different payload. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - sandboxesLifecycle + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + x-sbx-resource-response: + $ref: "#/components/schemas/Sandbox" + /v1/sandboxes/{sandbox}/stop: + post: + tags: + - Sandboxes + summary: StopSandbox stops a running sandbox; a backend advertising memory preservation hibernates it. + description: |- + StopSandbox stops a running sandbox; a backend advertising memory preservation hibernates it. + Action: a lifecycle transition is not a representation a caller can PUT. + operationId: stopSandbox + parameters: + - name: sandbox + in: path + description: The sandbox id. + required: true + schema: + type: string + - name: Idempotency-Key + in: header + description: Replay key. Reusing it with a different payload fails with failedPrecondition; accepted keys are retained for at least 24 hours. + schema: + type: string + title: Idempotency-Key + description: Idempotency key. A replay returns the first result; the same key with a different payload fails with FAILED_PRECONDITION; retained at least 24 hours. + - name: If-Match + in: header + description: "The complete quoted strong entity-tag observed on the resource, including its quotes. Every mutation of an existing resource requires it: an absent value answers 428 and a stale one 412, each carrying failedPrecondition in the body." + required: true + schema: + type: string + title: If-Match + description: |- + etag is the required precondition, the value the target's last read returned. + An absent one refuses etag_required and a stale one etag_mismatch. + requestBody: + content: + application/json: + schema: + type: object + properties: {} + title: StopSandboxRequest + additionalProperties: false + description: StopSandboxRequest stops a sandbox; a backend advertising memory preservation hibernates it. + required: false + responses: + "200": + description: Success + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Sandbox" + - not: + properties: + core: + properties: + status: + enum: + - stopping + required: + - status + required: + - core + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "202": + description: Accepted. The resource is still progressing; read it or follow its events until completion. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Sandbox" + - properties: + core: + properties: + status: + enum: + - stopping + required: + - status + required: + - core + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "409": + description: The Idempotency-Key was already used with a different payload. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + failedPrecondition: + summary: The Idempotency-Key was already used with a different payload. + value: + code: failedPrecondition + message: The Idempotency-Key was already used with a different payload. + "412": + description: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_mismatch: + summary: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + value: + code: failedPrecondition + message: "etag_mismatch: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value." + details: + - "@type": type.googleapis.com/docker.sandboxes.v1.EtagMismatch + currentEtag: v2 + "428": + description: No precondition was sent. The body carries failedPrecondition naming etag_required. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_required: + summary: No precondition was sent. The body carries failedPrecondition naming etag_required. + value: + code: failedPrecondition + message: "etag_required: No precondition was sent. The body carries failedPrecondition naming etag_required." + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: FAILED_PRECONDITION + description: The Idempotency-Key was already used with a different payload. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - sandboxesLifecycle + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + x-sbx-resource-response: + $ref: "#/components/schemas/Sandbox" + /v1/secrets: + get: + tags: + - Secrets + summary: ListSecrets lists secret metadata in the caller's owner scope. + description: |- + ListSecrets lists secret metadata in the caller's owner scope. + + Returns unimplemented when this operation is unavailable. Support does not grant permission. + operationId: listSecrets + parameters: + - name: pageSize + in: query + description: |- + page_size is an optional page size. + + Omitted or zero uses the backend default. Unless the operation states otherwise, page-size limits and handling of larger requests are backend-specific; use the backend support guide. Continue with nextPageToken until it is empty. + schema: + type: integer + title: page_size + required: false + - name: pageToken + in: query + description: page_token is an opaque continuation token. + schema: + type: string + title: page_token + required: false + - name: filter + in: query + description: |- + filter is comma-separated exact-match field=value terms; the portable fields are + name, display_name and uid. + schema: + type: string + title: filter + required: false + - name: orderBy + in: query + description: |- + order_by is a single order field with optional direction; the portable fields are + created_at and name. + schema: + type: string + title: order_by + required: false + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ListSecretsResponse" + "501": + description: This operation is not available on this service. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + unimplemented: + summary: This operation is not available on this service. + value: + code: unimplemented + message: This operation is not available on this service. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: UNIMPLEMENTED + description: This operation is not available on this service. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - secretsRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + post: + tags: + - Secrets + summary: CreateSecret stores new secret material. + description: |- + CreateSecret stores new secret material. + + Returns unimplemented when this operation is unavailable. Support does not grant permission. + operationId: createSecret + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateSecretRequest" + required: true + responses: + "201": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Secret" + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "409": + description: |- + Creation conflicts with an existing resource (ALREADY_EXISTS). + + The Idempotency-Key was already used with a different payload. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + failedPrecondition: + summary: The Idempotency-Key was already used with a different payload. + value: + code: failedPrecondition + message: The Idempotency-Key was already used with a different payload. + "501": + description: This operation is not available on this service. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + unimplemented: + summary: This operation is not available on this service. + value: + code: unimplemented + message: This operation is not available on this service. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: UNIMPLEMENTED + description: This operation is not available on this service. + - code: FAILED_PRECONDITION + description: The Idempotency-Key was already used with a different payload. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - secretsWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + parameters: + - name: Idempotency-Key + in: header + description: Replay key. Reusing it with a different payload fails with failedPrecondition; accepted keys are retained for at least 24 hours. + schema: + type: string + title: Idempotency-Key + description: Idempotency key. A replay returns the first result; the same key with a different payload fails with FAILED_PRECONDITION; retained at least 24 hours. + /v1/secrets/{secret}: + get: + tags: + - Secrets + summary: GetSecret reads one secret's metadata; material is never returned. + description: |- + GetSecret reads one secret's metadata; material is never returned. + + Returns unimplemented when this operation is unavailable. Support does not grant permission. + operationId: getSecret + parameters: + - name: secret + in: path + description: The secret id. + required: true + schema: + type: string + - name: If-None-Match + in: header + description: A quoted entity-tag or comma-separated list, compared weakly with the current resource etag. A match, including a weak form or wildcard *, answers 304 without a body after authorization. Malformed input is treated as no match. + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Secret" + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "304": + description: The resource's etag equals the If-None-Match value, so this response carries no body. + "404": + description: The target is absent or not visible within the caller's scope. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + notFound: + summary: The target is absent or not visible within the caller's scope. + value: + code: notFound + message: The target is absent or not visible within the caller's scope. + "501": + description: This operation is not available on this service. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + unimplemented: + summary: This operation is not available on this service. + value: + code: unimplemented + message: This operation is not available on this service. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: NOT_FOUND + description: The target is absent or not visible within the caller's scope. + - code: UNIMPLEMENTED + description: This operation is not available on this service. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - secretsRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + put: + tags: + - Secrets + summary: UpdateSecret replaces secret metadata and material. + description: |- + UpdateSecret replaces secret metadata and material. + + Returns unimplemented when this operation is unavailable. Support does not grant permission. + operationId: updateSecret + parameters: + - name: secret + in: path + description: The secret id. + required: true + schema: + type: string + - name: Idempotency-Key + in: header + description: Replay key. Reusing it with a different payload fails with failedPrecondition; accepted keys are retained for at least 24 hours. + schema: + type: string + title: Idempotency-Key + description: Idempotency key. A replay returns the first result; the same key with a different payload fails with FAILED_PRECONDITION; retained at least 24 hours. + - name: If-Match + in: header + description: "The complete quoted strong entity-tag observed on the resource, including its quotes. Every mutation of an existing resource requires it: an absent value answers 428 and a stale one 412, each carrying failedPrecondition in the body." + required: true + schema: + type: string + title: If-Match + description: |- + etag is the required precondition, the value the target's last read returned. + An absent one refuses etag_required and a stale one etag_mismatch. + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateSecretBody" + required: true + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Secret" + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "409": + description: The Idempotency-Key was already used with a different payload. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + failedPrecondition: + summary: The Idempotency-Key was already used with a different payload. + value: + code: failedPrecondition + message: The Idempotency-Key was already used with a different payload. + "412": + description: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_mismatch: + summary: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + value: + code: failedPrecondition + message: "etag_mismatch: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value." + details: + - "@type": type.googleapis.com/docker.sandboxes.v1.EtagMismatch + currentEtag: v2 + "428": + description: No precondition was sent. The body carries failedPrecondition naming etag_required. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_required: + summary: No precondition was sent. The body carries failedPrecondition naming etag_required. + value: + code: failedPrecondition + message: "etag_required: No precondition was sent. The body carries failedPrecondition naming etag_required." + "501": + description: This operation is not available on this service. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + unimplemented: + summary: This operation is not available on this service. + value: + code: unimplemented + message: This operation is not available on this service. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: UNIMPLEMENTED + description: This operation is not available on this service. + - code: FAILED_PRECONDITION + description: The Idempotency-Key was already used with a different payload. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - secretsWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + delete: + tags: + - Secrets + summary: DeleteSecret deletes a secret; deleting an already absent secret succeeds. + description: |- + DeleteSecret deletes a secret; deleting an already absent secret succeeds. + Its response carries the credential fence receipt, so it answers 200 with a + body rather than the 204 an empty-response delete declares. + + Returns unimplemented when this operation is unavailable. Support does not grant permission. + operationId: deleteSecret + parameters: + - name: secret + in: path + description: The secret id. + required: true + schema: + type: string + - name: If-Match + in: header + description: "The complete quoted strong entity-tag observed on the resource, including its quotes. Every mutation of an existing resource requires it: an absent value answers 428 and a stale one 412, each carrying failedPrecondition in the body." + required: true + schema: + type: string + title: If-Match + description: |- + etag is the required precondition, the value the target's last read returned. + An absent one refuses etag_required and a stale one etag_mismatch. + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/DeleteSecretResponse" + "412": + description: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_mismatch: + summary: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + value: + code: failedPrecondition + message: "etag_mismatch: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value." + details: + - "@type": type.googleapis.com/docker.sandboxes.v1.EtagMismatch + currentEtag: v2 + "428": + description: No precondition was sent. The body carries failedPrecondition naming etag_required. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_required: + summary: No precondition was sent. The body carries failedPrecondition naming etag_required. + value: + code: failedPrecondition + message: "etag_required: No precondition was sent. The body carries failedPrecondition naming etag_required." + "501": + description: This operation is not available on this service. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + unimplemented: + summary: This operation is not available on this service. + value: + code: unimplemented + message: This operation is not available on this service. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: UNIMPLEMENTED + description: This operation is not available on this service. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - secretsWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/snapshots: + get: + tags: + - Snapshots + summary: ListSnapshots lists snapshots in the caller's owner scope. + description: ListSnapshots lists snapshots in the caller's owner scope. + operationId: listSnapshots + parameters: + - name: sandbox + in: query + description: (OPTIONAL) sandbox is the complete resource name, in the form sandboxes/{sandbox}. + required: false + schema: + type: string + title: sandbox + pattern: ^(?:sandboxes/[^/]+)?$ + - name: pageSize + in: query + description: |- + page_size is an optional page size. + + Omitted or zero uses the backend default. Unless the operation states otherwise, page-size limits and handling of larger requests are backend-specific; use the backend support guide. Continue with nextPageToken until it is empty. + schema: + type: integer + title: page_size + required: false + - name: pageToken + in: query + description: page_token is an opaque continuation token. + schema: + type: string + title: page_token + required: false + - name: filter + in: query + description: |- + filter is comma-separated exact-match field=value terms; the portable fields are + name, display_name and uid. + schema: + type: string + title: filter + required: false + - name: orderBy + in: query + description: |- + order_by is a single order field with optional direction; the portable fields are + created_at and name. + schema: + type: string + title: order_by + required: false + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ListSnapshotsResponse" + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - snapshotsRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/snapshots/{snapshot}: + get: + tags: + - Snapshots + summary: GetSnapshot reads one snapshot by its resource name. + description: GetSnapshot reads one snapshot by its resource name. + operationId: getSnapshot + parameters: + - name: snapshot + in: path + description: The snapshot id. + required: true + schema: + type: string + - name: If-None-Match + in: header + description: A quoted entity-tag or comma-separated list, compared weakly with the current resource etag. A match, including a weak form or wildcard *, answers 304 without a body after authorization. Malformed input is treated as no match. + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Snapshot" + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "304": + description: The resource's etag equals the If-None-Match value, so this response carries no body. + "404": + description: The target is absent or not visible within the caller's scope. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + notFound: + summary: The target is absent or not visible within the caller's scope. + value: + code: notFound + message: The target is absent or not visible within the caller's scope. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: NOT_FOUND + description: The target is absent or not visible within the caller's scope. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - snapshotsRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + delete: + tags: + - Snapshots + summary: DeleteSnapshot deletes a snapshot; deleting an already absent snapshot succeeds. + description: DeleteSnapshot deletes a snapshot; deleting an already absent snapshot succeeds. + operationId: deleteSnapshot + parameters: + - name: snapshot + in: path + description: The snapshot id. + required: true + schema: + type: string + - name: If-Match + in: header + description: "The complete quoted strong entity-tag observed on the resource, including its quotes. Every mutation of an existing resource requires it: an absent value answers 428 and a stale one 412, each carrying failedPrecondition in the body." + required: true + schema: + type: string + title: If-Match + description: |- + etag is the required precondition, the value the target's last read returned. + An absent one refuses etag_required and a stale one etag_mismatch. + responses: + "204": + description: Success. The response carries no body. + "409": + description: |- + The target's preparation, capture, or import has not finished; deletion is refused until it settles to a terminal state. + + An admitted consumer or live resource holds a content lease on the target; deletion is refused while any lease remains held. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + deletion_preparation_incomplete: + summary: The target's preparation, capture, or import has not finished; deletion is refused until it settles to a terminal state. + value: + code: failedPrecondition + message: "deletion_preparation_incomplete: The target's preparation, capture, or import has not finished; deletion is refused until it settles to a terminal state." + deletion_content_lease_held: + summary: An admitted consumer or live resource holds a content lease on the target; deletion is refused while any lease remains held. + value: + code: failedPrecondition + message: "deletion_content_lease_held: An admitted consumer or live resource holds a content lease on the target; deletion is refused while any lease remains held." + "412": + description: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_mismatch: + summary: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + value: + code: failedPrecondition + message: "etag_mismatch: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value." + details: + - "@type": type.googleapis.com/docker.sandboxes.v1.EtagMismatch + currentEtag: v2 + "428": + description: No precondition was sent. The body carries failedPrecondition naming etag_required. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_required: + summary: No precondition was sent. The body carries failedPrecondition naming etag_required. + value: + code: failedPrecondition + message: "etag_required: No precondition was sent. The body carries failedPrecondition naming etag_required." + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: FAILED_PRECONDITION + description: The target's preparation, capture, or import has not finished; deletion is refused until it settles to a terminal state. + reason: deletion_preparation_incomplete + - code: FAILED_PRECONDITION + description: An admitted consumer or live resource holds a content lease on the target; deletion is refused while any lease remains held. + reason: deletion_content_lease_held + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - snapshotsWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + /v1/snapshots/{snapshot}/restore: + post: + tags: + - Snapshots + summary: RestoreSnapshot forks a new sandbox from a snapshot. + description: |- + RestoreSnapshot forks a new sandbox from a snapshot. + Action: a restore creates a sandbox, so the snapshot in the path is the source and not + the created resource. + operationId: restoreSnapshot + parameters: + - name: snapshot + in: path + description: The snapshot id. + required: true + schema: + type: string + - name: Idempotency-Key + in: header + description: Replay key. Reusing it with a different payload fails with failedPrecondition; accepted keys are retained for at least 24 hours. + schema: + type: string + title: Idempotency-Key + description: Idempotency key. A replay returns the first result; the same key with a different payload fails with FAILED_PRECONDITION; retained at least 24 hours. + requestBody: + content: + application/json: + schema: + type: object + properties: + displayName: + type: string + maxLength: 64 + pattern: ^[a-zA-Z0-9_-]*$ + description: (OPTIONAL) display_name is a scoped label; the backend assigns the immutable resource ID. + title: RestoreSnapshotRequest + additionalProperties: false + description: RestoreSnapshotRequest creates a new sandbox from one snapshot. + required: true + responses: + "201": + description: Success + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Sandbox" + - not: + properties: + core: + properties: + status: + enum: + - creating + - starting + required: + - status + required: + - core + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "202": + description: Accepted. The resource is still progressing; read it or follow its events until completion. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Sandbox" + - properties: + core: + properties: + status: + enum: + - creating + - starting + required: + - status + required: + - core + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "409": + description: |- + Creation conflicts with an existing resource (ALREADY_EXISTS). + + The Idempotency-Key was already used with a different payload. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + failedPrecondition: + summary: The Idempotency-Key was already used with a different payload. + value: + code: failedPrecondition + message: The Idempotency-Key was already used with a different payload. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: FAILED_PRECONDITION + description: The Idempotency-Key was already used with a different payload. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - snapshotsRead + - sandboxesCreate + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + x-sbx-resource-response: + $ref: "#/components/schemas/Sandbox" + /v1/volumes: + get: + tags: + - Volumes + summary: ListVolumes lists volumes in the caller's owner scope. + description: |- + ListVolumes lists volumes in the caller's owner scope. + + Returns unimplemented when this operation is unavailable. Support does not grant permission. + operationId: listVolumes + parameters: + - name: pageSize + in: query + description: |- + page_size is an optional page size. + + Omitted or zero uses the backend default. Unless the operation states otherwise, page-size limits and handling of larger requests are backend-specific; use the backend support guide. Continue with nextPageToken until it is empty. + schema: + type: integer + title: page_size + required: false + - name: pageToken + in: query + description: page_token is an opaque continuation token. + schema: + type: string + title: page_token + required: false + - name: filter + in: query + description: |- + filter is comma-separated exact-match field=value terms; the portable fields are + name, display_name and uid. + schema: + type: string + title: filter + required: false + - name: orderBy + in: query + description: |- + order_by is a single order field with optional direction; the portable fields are + created_at and name. + schema: + type: string + title: order_by + required: false + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ListVolumesResponse" + "501": + description: This operation is not available on this service. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + unimplemented: + summary: This operation is not available on this service. + value: + code: unimplemented + message: This operation is not available on this service. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: UNIMPLEMENTED + description: This operation is not available on this service. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - volumesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + post: + tags: + - Volumes + summary: CreateVolume creates a persistent volume. + description: |- + CreateVolume creates a persistent volume. + + Returns unimplemented when this operation is unavailable. Support does not grant permission. + operationId: createVolume + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateVolumeRequest" + required: true + responses: + "201": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Volume" + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "409": + description: |- + Creation conflicts with an existing resource (ALREADY_EXISTS). + + The Idempotency-Key was already used with a different payload. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + failedPrecondition: + summary: The Idempotency-Key was already used with a different payload. + value: + code: failedPrecondition + message: The Idempotency-Key was already used with a different payload. + "501": + description: This operation is not available on this service. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + unimplemented: + summary: This operation is not available on this service. + value: + code: unimplemented + message: This operation is not available on this service. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: UNIMPLEMENTED + description: This operation is not available on this service. + - code: FAILED_PRECONDITION + description: The Idempotency-Key was already used with a different payload. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - volumesWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + parameters: + - name: Idempotency-Key + in: header + description: Replay key. Reusing it with a different payload fails with failedPrecondition; accepted keys are retained for at least 24 hours. + schema: + type: string + title: Idempotency-Key + description: Idempotency key. A replay returns the first result; the same key with a different payload fails with FAILED_PRECONDITION; retained at least 24 hours. + /v1/volumes/{volume}: + get: + tags: + - Volumes + summary: GetVolume reads one volume by its resource name. + description: |- + GetVolume reads one volume by its resource name. + + Returns unimplemented when this operation is unavailable. Support does not grant permission. + operationId: getVolume + parameters: + - name: volume + in: path + description: The volume id. + required: true + schema: + type: string + - name: If-None-Match + in: header + description: A quoted entity-tag or comma-separated list, compared weakly with the current resource etag. A match, including a weak form or wildcard *, answers 304 without a body after authorization. Malformed input is treated as no match. + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Volume" + headers: + ETag: + description: The etag of the resource this response carries, which the next mutation of it sends as If-Match. + schema: + type: string + "304": + description: The resource's etag equals the If-None-Match value, so this response carries no body. + "404": + description: The target is absent or not visible within the caller's scope. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + notFound: + summary: The target is absent or not visible within the caller's scope. + value: + code: notFound + message: The target is absent or not visible within the caller's scope. + "501": + description: This operation is not available on this service. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + unimplemented: + summary: This operation is not available on this service. + value: + code: unimplemented + message: This operation is not available on this service. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: NOT_FOUND + description: The target is absent or not visible within the caller's scope. + - code: UNIMPLEMENTED + description: This operation is not available on this service. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - volumesRead + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false + delete: + tags: + - Volumes + summary: DeleteVolume deletes a volume; deleting an already absent volume succeeds. + description: |- + DeleteVolume deletes a volume; deleting an already absent volume succeeds. + + Returns unimplemented when this operation is unavailable. Support does not grant permission. + operationId: deleteVolume + parameters: + - name: volume + in: path + description: The volume id. + required: true + schema: + type: string + - name: If-Match + in: header + description: "The complete quoted strong entity-tag observed on the resource, including its quotes. Every mutation of an existing resource requires it: an absent value answers 428 and a stale one 412, each carrying failedPrecondition in the body." + required: true + schema: + type: string + title: If-Match + description: |- + etag is the required precondition, the value the target's last read returned. + An absent one refuses etag_required and a stale one etag_mismatch. + responses: + "204": + description: Success. The response carries no body. + "412": + description: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_mismatch: + summary: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value. + value: + code: failedPrecondition + message: "etag_mismatch: The precondition is not the resource's current etag. The body carries failedPrecondition naming etag_mismatch and an EtagMismatch detail with the current value." + details: + - "@type": type.googleapis.com/docker.sandboxes.v1.EtagMismatch + currentEtag: v2 + "428": + description: No precondition was sent. The body carries failedPrecondition naming etag_required. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + etag_required: + summary: No precondition was sent. The body carries failedPrecondition naming etag_required. + value: + code: failedPrecondition + message: "etag_required: No precondition was sent. The body carries failedPrecondition naming etag_required." + "501": + description: This operation is not available on this service. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + unimplemented: + summary: This operation is not available on this service. + value: + code: unimplemented + message: This operation is not available on this service. + default: + description: The structured Error body identifies the failure with a stable code and optional typed details. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + x-sbx-error-responses: + - code: UNIMPLEMENTED + description: This operation is not available on this service. + security: + - bearer: [] + x-sbx-serving-surface: management + x-sbx-plane: control + x-sbx-required-permissions: + - volumesWrite + x-sbx-conditional-permissions: [] + x-sbx-authenticated-only: false +components: + schemas: + Error: + type: object + title: Error + required: + - code + description: An HTTP refusal, resource failure, or terminal stream error. Clients preserve unknown codes and structured details. + properties: + code: + anyOf: + - $ref: "#/components/schemas/ErrorCode" + - type: string + message: + type: string + details: + type: array + items: + $ref: "#/components/schemas/ErrorDetail" + examples: + - code: notFound + details: [] + message: sandbox not found + ErrorCode: + type: string + enum: + - canceled + - unknown + - invalidArgument + - deadlineExceeded + - notFound + - alreadyExists + - permissionDenied + - resourceExhausted + - failedPrecondition + - aborted + - outOfRange + - unimplemented + - internal + - unavailable + - dataLoss + - unauthenticated + description: Known public failure categories. Error.code also accepts future strings. + ErrorDetail: + oneOf: + - $ref: "#/components/schemas/BadRequestErrorDetail" + - $ref: "#/components/schemas/DebugInfoErrorDetail" + - $ref: "#/components/schemas/ErrorInfoErrorDetail" + - $ref: "#/components/schemas/HelpErrorDetail" + - $ref: "#/components/schemas/LocalizedMessageErrorDetail" + - $ref: "#/components/schemas/PreconditionFailureErrorDetail" + - $ref: "#/components/schemas/QuotaFailureErrorDetail" + - $ref: "#/components/schemas/RequestInfoErrorDetail" + - $ref: "#/components/schemas/ResourceInfoErrorDetail" + - $ref: "#/components/schemas/RetryInfoErrorDetail" + - $ref: "#/components/schemas/ValidationViolationsErrorDetail" + - $ref: "#/components/schemas/EtagMismatchErrorDetail" + - $ref: "#/components/schemas/KitFieldOutcomeReportErrorDetail" + - $ref: "#/components/schemas/McpConfigurationReportErrorDetail" + - $ref: "#/components/schemas/UnknownErrorDetail" + discriminator: + propertyName: "@type" + mapping: + type.googleapis.com/google.rpc.BadRequest: "#/components/schemas/BadRequestErrorDetail" + type.googleapis.com/google.rpc.DebugInfo: "#/components/schemas/DebugInfoErrorDetail" + type.googleapis.com/google.rpc.ErrorInfo: "#/components/schemas/ErrorInfoErrorDetail" + type.googleapis.com/google.rpc.Help: "#/components/schemas/HelpErrorDetail" + type.googleapis.com/google.rpc.LocalizedMessage: "#/components/schemas/LocalizedMessageErrorDetail" + type.googleapis.com/google.rpc.PreconditionFailure: "#/components/schemas/PreconditionFailureErrorDetail" + type.googleapis.com/google.rpc.QuotaFailure: "#/components/schemas/QuotaFailureErrorDetail" + type.googleapis.com/google.rpc.RequestInfo: "#/components/schemas/RequestInfoErrorDetail" + type.googleapis.com/google.rpc.ResourceInfo: "#/components/schemas/ResourceInfoErrorDetail" + type.googleapis.com/google.rpc.RetryInfo: "#/components/schemas/RetryInfoErrorDetail" + type.googleapis.com/buf.validate.Violations: "#/components/schemas/ValidationViolationsErrorDetail" + type.googleapis.com/docker.sandboxes.v1.EtagMismatch: "#/components/schemas/EtagMismatchErrorDetail" + type.googleapis.com/docker.sandboxes.v1.KitFieldOutcomeReport: "#/components/schemas/KitFieldOutcomeReportErrorDetail" + type.googleapis.com/docker.sandboxes.v1.McpConfigurationReport: "#/components/schemas/McpConfigurationReportErrorDetail" + UnknownErrorDetail: + type: object + required: + - "@type" + additionalProperties: true + description: A structured error detail identified by @type. Its decoded JSON fields appear beside the type URL. + properties: + "@type": + type: string + not: + enum: + - type.googleapis.com/google.rpc.BadRequest + - type.googleapis.com/google.rpc.DebugInfo + - type.googleapis.com/google.rpc.ErrorInfo + - type.googleapis.com/google.rpc.Help + - type.googleapis.com/google.rpc.LocalizedMessage + - type.googleapis.com/google.rpc.PreconditionFailure + - type.googleapis.com/google.rpc.QuotaFailure + - type.googleapis.com/google.rpc.RequestInfo + - type.googleapis.com/google.rpc.ResourceInfo + - type.googleapis.com/google.rpc.RetryInfo + - type.googleapis.com/buf.validate.Violations + - type.googleapis.com/docker.sandboxes.v1.EtagMismatch + - type.googleapis.com/docker.sandboxes.v1.KitFieldOutcomeReport + - type.googleapis.com/docker.sandboxes.v1.McpConfigurationReport + ValidationFieldPath: + type: object + properties: + elements: + type: array + items: + $ref: "#/components/schemas/ValidationFieldPathElement" + description: "`elements` contains each element of the path, starting from the root and recursing downward." + title: FieldPath + description: A path to a field, including any parent fields. + ValidationFieldPathElement: + type: object + allOf: + - properties: + fieldNumber: + type: + - integer + - "null" + format: int32 + description: "`field_number` is the field number this path element refers to." + fieldName: + type: + - string + - "null" + description: |- + `field_name` contains the field name this path element refers to. + This can be used to display a human-readable path even if the field number is unknown. + fieldType: + oneOf: + - $ref: "#/components/schemas/ValidationFieldType" + - type: "null" + description: The type of the field that failed validation. + keyType: + oneOf: + - $ref: "#/components/schemas/ValidationFieldType" + - type: "null" + description: |- + `key_type` specifies the map key type of this field. This value is useful when traversing + unknown fields through wire data: specifically, it allows handling the differences between + different integer encodings. + valueType: + oneOf: + - $ref: "#/components/schemas/ValidationFieldType" + - type: "null" + description: |- + `value_type` specifies map value type of this field. This is useful if you want to display a + value inside unknown fields through wire data. + - oneOf: + - type: object + properties: + boolKey: + type: boolean + description: "`bool_key` specifies a map key of type bool." + title: bool_key + required: + - boolKey + - type: object + properties: + index: + type: + - integer + - string + format: int64 + description: "`index` specifies a 0-based index into a repeated field." + title: index + required: + - index + - type: object + properties: + intKey: + type: + - integer + - string + format: int64 + description: "`int_key` specifies a map key of type int32, int64, sint32, sint64, sfixed32 or sfixed64." + title: int_key + required: + - intKey + - type: object + properties: + stringKey: + type: string + description: "`string_key` specifies a map key of type string." + title: string_key + required: + - stringKey + - type: object + properties: + uintKey: + type: + - integer + - string + format: int64 + description: "`uint_key` specifies a map key of type uint32, uint64, fixed32 or fixed64." + title: uint_key + required: + - uintKey + title: FieldPathElement + description: One field in a validation path. For a map or list, the subscript identifies the selected element. + ValidationViolation: + type: object + properties: + field: + oneOf: + - $ref: "#/components/schemas/ValidationFieldPath" + - type: "null" + description: The field that failed validation, including its parent fields. + rule: + oneOf: + - $ref: "#/components/schemas/ValidationFieldPath" + - type: "null" + description: The validation rule that failed. + ruleId: + type: + - string + - "null" + description: |- + `rule_id` is the unique identifier of the `Rule` that was not fulfilled. + This is the same `id` that was specified in the `Rule` message, allowing easy tracing of which rule was violated. + message: + type: + - string + - "null" + description: |- + `message` is a human-readable error message that describes the nature of the violation. + This can be the default error message from the violated `Rule`, or it can be a custom message that gives more context about the violation. + forKey: + type: + - boolean + - "null" + description: "`for_key` indicates whether the violation was caused by a map key, rather than a value." + title: Violation + description: A validation failure, with the affected field, rule, and a human-readable message. + ValidationViolations: + type: object + properties: + violations: + type: array + items: + $ref: "#/components/schemas/ValidationViolation" + description: "`violations` is a repeated field that contains all the `Violation` messages corresponding to the violations detected." + title: Violations + description: The validation failures returned for a request. + ValidationViolationsErrorDetail: + type: object + properties: + violations: + type: array + items: + $ref: "#/components/schemas/ValidationViolation" + description: "`violations` is a repeated field that contains all the `Violation` messages corresponding to the violations detected." + "@type": + type: string + const: type.googleapis.com/buf.validate.Violations + title: ViolationsErrorDetail + description: The validation failures returned for a request. + required: + - "@type" + AppliedKit: + type: object + properties: + ref: + type: string + description: ref is the kit reference supplied or resolved by the backend. + kind: + type: string + enum: + - sandbox + - mixin + description: kind is normalized to "sandbox" or "mixin". + appliedAt: + description: applied_at is when the kit became active. + $ref: "#/components/schemas/Timestamp" + digest: + type: string + description: digest identifies the exact applied content. + title: AppliedKit + required: + - kind + description: AppliedKit records kit content that was applied to a sandbox. + EndpointAuthentication: + type: object + properties: + scheme: + not: + enum: + - unspecified + $ref: "#/components/schemas/EndpointAuthenticationScheme" + defaultTtl: + description: default_ttl and max_ttl describe CreateEndpointCredential's provider policy. + $ref: "#/components/schemas/Duration" + maxTtl: + $ref: "#/components/schemas/Duration" + credentialTransports: + type: array + items: + $ref: "#/components/schemas/EndpointCredentialTransport" + uniqueItems: true + description: Omission retains Authorization-header clients; browsers require an explicit subprotocol offer. + title: EndpointAuthentication + required: + - scheme + description: |- + EndpointAuthentication describes credential issuance without publishing a token. + local sockets cannot advertise bearer credential transports + scoped bearer discovery states the five-minute credential lifetime; local sockets carry no token lifetime + dependentSchemas: + scheme: + allOf: + - oneOf: + - required: + - defaultTtl + - maxTtl + properties: + scheme: + const: scopedBearer + defaultTtl: + pattern: ^300([.]0{1,9})?s$ + maxTtl: + pattern: ^300([.]0{1,9})?s$ + - properties: + scheme: + const: localSocket + not: + anyOf: + - required: + - defaultTtl + - required: + - maxTtl + credentialTransports: + allOf: + - if: + properties: + scheme: + const: localSocket + then: + properties: + credentialTransports: + maxItems: 0 + EndpointAuthenticationScheme: + type: string + title: EndpointAuthenticationScheme + enum: + - unspecified + - scopedBearer + - localSocket + description: |- + Values: + unspecified + scopedBearer + localSocket + EndpointCredentialTransport: + type: string + title: EndpointCredentialTransport + enum: + - unspecified + - authorizationHeader + - webSocketSubprotocol + description: |- + Values: + unspecified + authorizationHeader + webSocketSubprotocol + KitRef: + type: object + properties: + ref: + type: string + minLength: 1 + description: ref names the kit source. + kind: + type: string + enum: + - "" + - sandbox + - mixin + description: kind is "sandbox", "mixin", or empty to defer to the kit's own manifest. + title: KitRef + required: + - ref + additionalProperties: false + description: KitRef names a kit to apply. + Permission: + type: string + title: Permission + enum: + - unspecified + - sandboxesRead + - sandboxesCreate + - sandboxesExec + - sandboxesFilesRead + - sandboxesFilesWrite + - imagesPull + - secretsWrite + - sandboxesDelete + - sandboxesLifecycle + - sandboxesKits + - sandboxesPorts + - sandboxesCredential + - sandboxesSsh + - imagesRead + - imagesWrite + - secretsRead + - networkPoliciesRead + - volumesRead + - volumesWrite + - snapshotsRead + - snapshotsWrite + - mcpRead + - mcpWrite + - credentialsExchange + - sandboxesRename + - secretsResolve + - operator + description: The actions a credential is allowed to perform. Each operation declares its required permissions; service support does not grant permission. + Platform: + type: object + properties: + os: + type: string + description: os is the operating system, for example "linux". + architecture: + type: string + description: architecture is the CPU architecture, for example "amd64" or "arm64". + title: Platform + additionalProperties: false + description: Platform is the OCI platform tuple. + Protocol: + type: string + title: Protocol + enum: + - unspecified + - tcp + - udp + - tcp4 + - tcp6 + - udp4 + - udp6 + description: |- + Protocol names the transport protocol for a published port. + + Values: + unspecified: unspecified means the backend default, TCP. + tcp: tcp publishes TCP traffic. + udp: udp publishes UDP traffic. + tcp4: tcp4 publishes IPv4 TCP traffic. + tcp6: tcp6 publishes IPv6 TCP traffic. + udp4: udp4 publishes IPv4 UDP traffic. + udp6: udp6 publishes IPv6 UDP traffic. + PublishedPort: + type: object + properties: + hostIp: + type: string + description: host_ip is the local host bind address when relevant. + hostPort: + type: integer + description: host_port is the local host port when relevant. + sandboxPort: + type: integer + description: sandbox_port is the port inside the sandbox. + protocol: + description: protocol is the published protocol. + $ref: "#/components/schemas/Protocol" + url: + type: string + description: url is the required externally reachable address, including host-local addresses. + title: PublishedPort + description: PublishedPort reports external reachability for one sandbox port. + Resources: + type: object + properties: + cpus: + type: + - integer + - "null" + description: cpus is present only when the caller or backend chooses a CPU count. + memoryMib: + type: + - integer + - string + - "null" + format: int64 + description: memory_mib is present only when the caller or backend chooses memory. + title: Resources + additionalProperties: false + description: Resources describes sandbox CPU and memory allocation. + SandboxCore: + type: object + allOf: + - properties: + agent: + type: string + description: agent is the named agent profile, empty for raw image/start-command sandboxes. + status: + not: + enum: + - unspecified + description: status is the coarse lifecycle state. + readOnly: true + $ref: "#/components/schemas/SandboxStatus" + resources: + description: resources are the sandbox resources. + $ref: "#/components/schemas/Resources" + platform: + description: platform is the canonical placed platform. + $ref: "#/components/schemas/Platform" + environment: + type: object + additionalProperties: + type: string + title: value + description: environment is non-secret environment metadata. + ports: + type: array + items: + $ref: "#/components/schemas/PublishedPort" + description: ports are the currently published ports. + appliedKits: + type: array + items: + $ref: "#/components/schemas/AppliedKit" + description: applied_kits records kits active in the sandbox. + createdAt: + description: created_at is the creation time. + $ref: "#/components/schemas/Timestamp" + endpoint: + description: endpoint is where process and file services are served for this sandbox. + $ref: "#/components/schemas/SandboxEndpoint" + origin: + type: string + description: origin is set by merged views to identify the backend that owns this sandbox. + labels: + type: object + additionalProperties: + type: string + title: value + description: labels are non-secret caller-assigned key/value metadata. + etag: + type: string + minLength: 1 + pattern: ^"[^"\x00-\x20\x7f]*"$ + description: |- + etag identifies the observed version of this resource. It is opaque and strong, + changes on every visible change, and is what a mutation sends as its precondition. + readOnly: true + - properties: + image: + type: string + pattern: ^images/[^/]+$ + description: image is the managed Image resource name. + imageRef: + type: string + description: image_ref is an external OCI reference, with the existing backend validation. + dependentSchemas: + image: + not: + anyOf: + - required: + - imageRef + imageRef: + not: + anyOf: + - required: + - image + title: SandboxCore + required: + - status + - createdAt + - etag + description: SandboxCore is the backend-neutral sandbox view. At most one of image, imageRef is set. + SandboxEndpoint: + type: object + properties: + uri: + type: string + description: uri is the endpoint API base; preserve its path prefix when appending /v1 routes. + protocol: + description: protocol identifies the endpoint transport. + $ref: "#/components/schemas/SandboxEndpointProtocol" + credentialAudience: + type: string + description: credential_audience is server-controlled and required for network endpoints. + sandbox: + type: string + pattern: ^sandboxes/[^/]+$ + description: sandbox names the immutable sandbox incarnation served by this endpoint. + sandboxUid: + type: + - string + minLength: 1 + readOnly: true + apiVersion: + type: string + description: api_version identifies the /v1 endpoint contract before the client connects. + const: v1 + capabilities: + type: array + items: + type: string + pattern: ^[a-z][A-Za-z0-9]*$ + minItems: 1 + uniqueItems: true + description: capabilities lists supported public operation IDs, independently of caller grants. + authentication: + $ref: "#/components/schemas/EndpointAuthentication" + title: SandboxEndpoint + required: + - sandbox + - sandboxUid + - apiVersion + - capabilities + - authentication + description: |- + SandboxEndpoint tells a client where sandbox endpoint services are served. + network endpoints require scoped credentials; Unix sockets use actual socket access controls + sandbox must contain the endpoint's immutable sandbox UID + dependentSchemas: + authentication: + allOf: + - required: + - protocol + oneOf: + - required: + - credentialAudience + properties: + protocol: + const: http + credentialAudience: + minLength: 1 + authentication: + properties: + scheme: + const: scopedBearer + - properties: + protocol: + const: unixSocket + credentialAudience: + const: "" + authentication: + properties: + scheme: + const: localSocket + SandboxEndpointProtocol: + type: string + title: SandboxEndpointProtocol + enum: + - unspecified + - http + - unixSocket + description: |- + SandboxEndpointProtocol identifies the transport used by a sandbox endpoint. + + Values: + unspecified: unspecified is not a concrete endpoint protocol. + http: http is the REST API over HTTP. + unixSocket: unixSocket is a local unix socket. + SandboxStatus: + type: string + title: SandboxStatus + enum: + - unspecified + - creating + - starting + - running + - stopping + - stopped + - failed + - deleting + - degraded + description: |- + SandboxStatus is the backend-neutral lifecycle state. + + Values: + unspecified: unspecified is never a concrete known state. + creating: creating means provisioning has not completed. + starting: starting means a stopped sandbox is starting or resuming. + running: running means the sandbox is running. + stopping: stopping means the sandbox is stopping or hibernating. + stopped: stopped means the sandbox is not running and may be started. + failed: failed means the sandbox reached an unrecoverable failure. + deleting: deleting means deletion is accepted; reads continue until removal. + degraded: degraded means runtime contact is lost; recovery or failure may follow. + EffectiveCoreLifecycle: + type: object + properties: + stopMemoryOutcome: + not: + enum: + - unspecified + description: stop_memory_outcome is the selected stop/start guarantee for this sandbox. + $ref: "#/components/schemas/StopMemoryOutcome" + canCaptureMemory: + type: boolean + description: can_capture_memory is this sandbox's ALL snapshot support, independently set. + title: EffectiveCoreLifecycle + required: + - stopMemoryOutcome + description: EffectiveCoreLifecycle is persisted at admission and cannot silently degrade. + EffectiveFeatures: + type: object + properties: + lifecycle: + description: lifecycle is required even when no optional feature is selected. + $ref: "#/components/schemas/EffectiveCoreLifecycle" + outputRetention: + description: output_retention is the persisted per-process reconnect guarantee. + $ref: "#/components/schemas/OutputRetention" + managedVolumes: + description: managed_volumes records stable volume identities and concrete attachment modes. + $ref: "#/components/schemas/EffectiveManagedVolumes" + timeouts: + description: timeouts records frozen defaults and the currently armed deadline, if any. + $ref: "#/components/schemas/EffectiveTimeouts" + title: EffectiveFeatures + required: + - lifecycle + - outputRetention + description: EffectiveFeatures is emitted only through the composition representation. + FeatureOptions: + type: object + properties: + managedVolumes: + description: managed_volumes selects persistent resources and guest targets. + $ref: "#/components/schemas/ManagedVolumeOptions" + timeouts: + description: timeouts enables an explicit deadline; omission enables none. + $ref: "#/components/schemas/TimeoutOptions" + storedSecrets: + description: stored_secrets selects authorized stored references, never raw material. + $ref: "#/components/schemas/StoredSecretOptions" + title: FeatureOptions + additionalProperties: false + description: FeatureOptions carries typed admission intent; unknown fields are refused. + OutputRetention: + type: object + properties: + minDuration: + description: min_duration retains each process's output for at least this age when set. + $ref: "#/components/schemas/Duration" + minBytes: + type: + - integer + - string + format: int64 + description: |- + min_bytes retains at least this many recent bytes per process when nonzero. + a byte retention floor must be zero or at least 64 KiB + title: OutputRetention + description: |- + OutputRetention records minimum reconnect coverage independently of location. + output retention requires a duration or byte floor + StopMemoryOutcome: + type: string + title: StopMemoryOutcome + enum: + - unspecified + - discard + - preserve + description: |- + StopMemoryOutcome records the stop/start guarantee independently of location. + + Values: + unspecified: unspecified is not an advertised or accepted guarantee. + discard: discard retains disk and starts a fresh process tree. + preserve: preserve restores the stopped process tree on start. + FilesDownloadRequest: + type: object + properties: + paths: + type: array + items: + type: string + minItems: 1 + description: |- + paths naming stable regular files or absent entries at canonical absolute paths + yield contiguous results in request order, including duplicates. + title: DownloadRequest + required: + - paths + additionalProperties: false + description: DownloadRequest asks for one or more paths. + FilesDownloadResponse: + type: object + title: DownloadResponse + unevaluatedProperties: false + description: DownloadResponse is one frame in a download stream. At most one of data, error, header is set. + properties: + data: + type: string + format: byte + description: data carries bytes for the current file. + error: + description: error reports a path-level failure without aborting the whole stream. + $ref: "#/components/schemas/FileError" + header: + description: header starts a downloaded file. + $ref: "#/components/schemas/FileHeader" + dependentSchemas: + data: + not: + anyOf: + - required: + - error + - required: + - header + error: + not: + anyOf: + - required: + - data + - required: + - header + header: + not: + anyOf: + - required: + - data + - required: + - error + FileError: + type: object + properties: + path: + type: string + description: path is the path that failed. + failure: + description: failure retains the shared typed code and details for this path. + $ref: "#/components/schemas/Error" + title: FileError + required: + - failure + description: FileError reports a path-level file error. + FileHeader: + type: object + properties: + path: + type: string + minLength: 1 + description: path is the sandbox path. + mode: + type: integer + description: |- + mode carries Unix permission bits. Upload applies mode & 0777, or 0644 when + that value is zero, without subtracting umask; Download reports stored bits. + title: FileHeader + required: + - path + additionalProperties: false + description: FileHeader starts a file in an upload or download stream. + FileInfo: + type: object + properties: + path: + type: string + description: path is the sandbox path. + size: + type: + - integer + - string + format: int64 + description: size is the file size in bytes. + mode: + type: integer + description: mode is the stored Unix permission bits (0777), without type or special bits. + isDir: + type: boolean + description: |- + is_dir reports whether the path is a directory and stays populated on + every backend and every caller. type is the richer authority, and + type == directory iff is_dir. + type: + description: |- + type classifies the object, and Stat and List populate it for every + caller. Stat and List resolve an in-jail symlink and report + the resolved target's type; a link that cannot be resolved in-jail + (dangling, looping, or escaping) reports symlink for itself + rather than disclosing an out-of-jail target. + $ref: "#/components/schemas/FileType" + linkTarget: + type: string + description: |- + link_target is the literal readlink() text when type is symlink, + since that is the only case where nothing else lets the caller learn what + the link names; empty otherwise. + title: FileInfo + description: FileInfo is file metadata. + FileType: + type: string + title: FileType + enum: + - unspecified + - regular + - directory + - symlink + - other + description: |- + FileType classifies the object at a path. + + Values: + unspecified: unspecified means the backend has not classified the object. Stat and List always classify it. + regular: regular is a regular file. + directory: directory is a directory. + symlink: symlink is a symbolic link that could not be resolved to a target inside the jail. + other: other is a socket, FIFO, device, or other non-regular, non-directory, non-symlink object. + FilesListResponse: + type: object + properties: + entries: + type: array + items: + $ref: "#/components/schemas/FileInfo" + description: entries is the current page. + nextPageToken: + type: string + description: next_page_token is empty when there are no more pages. + title: ListResponse + description: ListResponse returns directory entries and a next-page token. + FilesMkdirRequest: + type: object + properties: + path: + type: string + minLength: 1 + description: path is the directory path. + mode: + type: integer + description: |- + mode supplies the new leaf's Unix permission bits: mode & 0777, or 0755 + when that value is zero, without subtracting umask. + parents: + type: boolean + description: parents creates missing parents when true. + title: MkdirRequest + required: + - path + additionalProperties: false + description: MkdirRequest creates a directory. + FilesMkdirResponse: + type: object + title: MkdirResponse + description: MkdirResponse has no fields. + FilesMoveRequest: + type: object + properties: + from: + type: string + minLength: 1 + description: from is the source path. + to: + type: string + minLength: 1 + description: to is the exact destination path. + title: MoveRequest + required: + - from + - to + additionalProperties: false + description: MoveRequest moves or renames a path. + FilesMoveResponse: + type: object + title: MoveResponse + description: MoveResponse has no fields. + FilesReadFileRequest: + type: object + properties: + path: + type: string + minLength: 1 + description: path is the sandbox path of a regular file. + title: ReadFileRequest + required: + - path + additionalProperties: false + description: ReadFileRequest names one regular file. + FilesReadFileResponse: + type: object + properties: + info: + description: info remains available to the internal adapter; HTTP clients use Stat for metadata. + $ref: "#/components/schemas/FileInfo" + data: + type: string + format: byte + description: data is the complete content; a file above the message cap is refused. + title: ReadFileResponse + required: + - info + additionalProperties: false + description: ReadFileResponse carries the whole file; HTTP selects data as its raw response body. + FilesRemoveResponse: + type: object + properties: + completedPaths: + type: array + items: + type: string + description: |- + completed_paths lists removed paths in traversal order, bounded to the + first 1000; completed_count carries the + true total when it exceeds that bound. + completedCount: + type: integer + description: |- + completed_count is the total number of paths removed, which may exceed + len(completed_paths). + failedPath: + type: string + description: |- + failed_path is the path where a recursive removal stopped; empty on full + success. Every path in completed_paths was removed before this one. + title: RemoveResponse + description: |- + RemoveResponse reports partial recursive progress to every caller: check failed_path, + not the operation status, when Remove stops partway. Effects are never rolled back. + FilesStatResponse: + type: object + properties: + info: + description: info is metadata for the path. + $ref: "#/components/schemas/FileInfo" + title: StatResponse + description: StatResponse returns file metadata. + FilesUploadRequest: + type: object + oneOf: + - type: object + properties: + data: + type: string + format: byte + description: data appends bytes to the current file. + title: data + required: + - data + - type: object + properties: + header: + description: |- + header starts a new file. The first frame, and the first frame after each + completed file, must be this arm. + $ref: "#/components/schemas/FileHeader" + title: header + required: + - header + title: UploadRequest + unevaluatedProperties: false + description: UploadRequest is one frame in an upload stream. + FilesUploadResponse: + type: object + properties: + filesWritten: + type: integer + description: files_written is the number of files successfully written. + title: UploadResponse + description: UploadResponse reports completed file writes. + FilesWriteFileRequest: + type: object + properties: + header: + description: header names the path and mode, as an Upload header does. + $ref: "#/components/schemas/FileHeader" + data: + type: string + format: byte + description: data is the complete new content; empty writes an empty file. + title: WriteFileRequest + required: + - header + additionalProperties: false + description: WriteFileRequest replaces one file. + FilesWriteFileResponse: + type: object + properties: + info: + description: info is the file's metadata after the write. + $ref: "#/components/schemas/FileInfo" + title: WriteFileResponse + required: + - info + description: WriteFileResponse reports the written file. + AttachmentMode: + type: string + title: AttachmentMode + enum: + - unspecified + - exclusive + description: |- + AttachmentMode fixes attachment ownership independently of backend placement. + + Values: + unspecified: unspecified selects exclusive read-write attachment. + exclusive: exclusive permits one sandbox to attach the volume read-write. + EffectiveManagedVolumes: + type: object + properties: + attachments: + type: array + items: + $ref: "#/components/schemas/EffectiveVolumeAttachment" + minItems: 1 + description: attachments retains admission order and stable volume identities. + title: EffectiveManagedVolumes + required: + - attachments + description: EffectiveManagedVolumes records storage that is not deleted with the sandbox. + EffectiveVolumeAttachment: + type: object + properties: + volume: + type: string + description: volume is the resource name recorded for this reference. + volumeUid: + type: + - string + - "null" + description: volume_uid retains the observed backing incarnation when the backend provides one. + readOnly: true + target: + type: string + minLength: 1 + description: target is the accepted normalized absolute guest path. + mode: + not: + enum: + - unspecified + description: mode is explicit and survives sandbox stop/start and backend restart. + $ref: "#/components/schemas/AttachmentMode" + title: EffectiveVolumeAttachment + required: + - target + - mode + description: EffectiveVolumeAttachment retains the identity, not a reusable name lookup. + ManagedVolumeOptions: + type: object + properties: + attachments: + type: array + items: + $ref: "#/components/schemas/VolumeAttachment" + minItems: 1 + description: attachments cannot repeat a volume identity or overlap another mount target. + title: ManagedVolumeOptions + required: + - attachments + additionalProperties: false + description: ManagedVolumeOptions selects existing volumes; it does not create storage. + VolumeAttachment: + type: object + properties: + volume: + type: string + pattern: ^volumes/[^/]+$ + description: volume is the complete resource name, in the form volumes/{volume}. + target: + type: string + minLength: 1 + description: target is an absolute guest path and cannot overlap another mount target. + mode: + description: mode defaults to exclusive; it never silently enables shared writers. + $ref: "#/components/schemas/AttachmentMode" + title: VolumeAttachment + required: + - volume + - target + additionalProperties: false + description: VolumeAttachment binds an existing owner-scoped volume before guest execution. + StoredSecretOptions: + type: object + properties: + secrets: + type: array + items: + type: string + pattern: ^secrets/[^/]+$ + minItems: 1 + description: secrets are complete Secret resource names resolved once at admission. + title: StoredSecretOptions + required: + - secrets + additionalProperties: false + description: StoredSecretOptions resolves owner-scoped references before the workload can run. + EffectiveTimeouts: + type: object + properties: + onTimeout: + not: + enum: + - unspecified + description: on_timeout remains the accepted disposition through renewal and restart. + $ref: "#/components/schemas/TimeoutAction" + autoResume: + type: boolean + description: auto_resume is explicit; endpoint authorization still precedes any wake-up. + initialTimeout: + description: Records the accepted initial TTL, not the recurring interval for RESTART or KEEP. + $ref: "#/components/schemas/Duration" + expiresAt: + description: expires_at is present only while a deadline is armed, not before first readiness or while stopped. + $ref: "#/components/schemas/Timestamp" + resumeTimeout: + description: Freezes the action's default for later starts; RESTART and KEEP manage their own interval. + $ref: "#/components/schemas/Duration" + title: EffectiveTimeouts + required: + - onTimeout + - initialTimeout + description: |- + EffectiveTimeouts retains the selected behavior and current action deadline. + DELETE and STOP retain a resume timeout; RESTART (and its deprecated alias KEEP) does not use one + TimeoutAction: + type: string + title: TimeoutAction + enum: + - unspecified + - delete + - stop + - keep + - restart + description: |- + TimeoutAction selects the lifecycle action taken at the reported deadline. + + Values: + unspecified: unspecified is invalid when explicitly supplied. + delete: delete deletes the sandbox at expiry. + stop: stop applies the sandbox's persisted stop guarantee at expiry. + keep: Deprecated alias for the restart action, still accepted on input and output. New clients should use the canonical restart action. + restart: Preserves memory through backend-managed stop/resume cycles at expiry. Requires memory preservation and the always-on entitlement. + TimeoutOptions: + type: object + properties: + timeout: + description: timeout is the initial TTL; omission selects the advertised action default. + $ref: "#/components/schemas/Duration" + onTimeout: + oneOf: + - $ref: "#/components/schemas/TimeoutAction" + - type: "null" + not: + enum: + - unspecified + description: on_timeout defaults to DELETE; an explicit UNSPECIFIED is invalid. + autoResume: + type: + - boolean + - "null" + description: Omission selects the advertised default; false does not disable RESTART or KEEP cycles. + title: TimeoutOptions + additionalProperties: false + description: |- + TimeoutOptions selects deadline behavior; presence enables this feature for the sandbox. + an explicit RESTART (or its deprecated alias KEEP) initial timeout must be at least one hour + ProcessAttach: + type: object + properties: + name: + type: string + pattern: ^processes/[^/]+$ + description: name is the complete resource name, in the form processes/{process}. + resumeFrom: + type: + - integer + - string + format: int64 + description: resume_from resumes after a received or published stream_sequence; 0 means from the start. Missing output after this point fails with FAILED_PRECONDITION. + title: Attach + required: + - name + additionalProperties: false + description: Attach selects the process and output resume point. + ProcessChunk: + type: object + properties: + stream: + description: stream identifies stdout, stderr, or PTY output. + $ref: "#/components/schemas/StreamType" + data: + type: string + format: byte + description: data is output bytes. + streamSequence: + type: + - integer + - string + format: int64 + description: stream_sequence is one-based and strictly increases per process output stream. + title: Chunk + description: Chunk carries process output. + CreateProcessRequest: + type: object + properties: + cmd: + type: array + items: + type: string + minItems: 1 + description: cmd is the argv vector. + env: + type: object + additionalProperties: + type: string + title: value + description: env adds environment variables. + workingDir: + type: string + description: working_dir is the process working directory. + user: + type: string + description: user is the user to run as. + pty: + description: pty requests a pseudo-terminal; absence uses separate stdin/stdout/stderr pipes. + $ref: "#/components/schemas/PtyConfig" + session: + type: string + description: session is an optional opaque session tag stored verbatim; empty means untagged. + label: + type: + - string + - "null" + minLength: 1 + description: (OPTIONAL) label is unique within the sandbox; omission retains the server-generated default. + title: CreateProcessRequest + required: + - cmd + additionalProperties: false + description: CreateProcessRequest starts an interactive process. + ExecRequest: + type: object + properties: + cmd: + type: array + items: + type: string + minItems: 1 + description: cmd is the argv vector. + env: + type: object + additionalProperties: + type: string + title: value + description: env adds environment variables. + workingDir: + type: string + description: working_dir is the process working directory. + user: + type: string + description: user is the user to run as. + title: ExecRequest + required: + - cmd + additionalProperties: false + description: ExecRequest runs a command to completion. + ExecResponse: + type: object + properties: + exitCode: + type: integer + format: int32 + description: exit_code is the process exit code. + stdout: + type: string + format: byte + description: stdout is captured stdout, up to the capture limit when incomplete. + stderr: + type: string + format: byte + description: stderr is captured stderr, up to the capture limit when incomplete. + incomplete: + type: boolean + description: |- + incomplete is true when the combined capture limit forced stdout and/or + stderr to discard some of the command's real output; false whenever + every byte the command produced was retained, even if that total lands + exactly at the limit. exit_code is still the real, final exit code: the + command ran to completion, only its captured output was capped. A retry + would rerun the command, so this stays a normal successful response + rather than an error a client might retry. + title: ExecResponse + description: ExecResponse returns process output after the command exits. + ProcessExited: + type: object + properties: + exitCode: + type: integer + format: int32 + description: exit_code is the process exit code. + reason: + type: string + description: reason is backend-readable exit context. + title: Exited + description: Exited reports process completion. + ProcessHeartbeat: + type: object + title: Heartbeat + additionalProperties: false + description: Heartbeat is an idle keepalive frame. + InteractRequest: + type: object + oneOf: + - type: object + properties: + attach: + description: attach opens or resumes the interaction. + $ref: "#/components/schemas/ProcessAttach" + title: attach + required: + - attach + - type: object + properties: + closeStdin: + type: boolean + description: |- + close_stdin closes standard input when true; false has no effect, including on a PTY. + On a PTY, true delivers the terminal's VEOF input through the line discipline and + does not close the PTY master; subsequent stdin frames remain valid. Terminal mode + owns interpretation: canonical mode treats VEOF as marking the end of input, while + raw mode delivers it as an ordinary input byte, so this promises delivery, never + interpretation. Each true frame on a PTY delivers exactly one VEOF input; unlike the + pipe path, repeated frames are not a no-op. + title: close_stdin + required: + - closeStdin + - type: object + properties: + resize: + description: resize changes terminal size; portable-range dimensions on a running process without a PTY return FAILED_PRECONDITION. + $ref: "#/components/schemas/TerminalSize" + title: resize + required: + - resize + - type: object + properties: + stdin: + type: string + format: byte + description: stdin writes process standard input. + title: stdin + required: + - stdin + title: InteractRequest + unevaluatedProperties: false + description: InteractRequest is one frame on the bidirectional interaction stream. + ListProcessesResponse: + type: object + properties: + processes: + type: array + items: + $ref: "#/components/schemas/Process" + description: processes is the current page. + nextPageToken: + type: string + description: next_page_token is empty when there are no more pages. + title: ListProcessesResponse + description: ListProcessesResponse returns processes and a next-page token. + ProcessOutput: + type: object + title: Output + unevaluatedProperties: false + description: Output is one server frame on an interaction stream. At most one of chunk, exited, heartbeat is set. + properties: + chunk: + description: chunk carries ordered process output. + $ref: "#/components/schemas/ProcessChunk" + exited: + description: exited reports process completion. + $ref: "#/components/schemas/ProcessExited" + heartbeat: + description: heartbeat keeps an idle stream alive and carries no resume position. + $ref: "#/components/schemas/ProcessHeartbeat" + dependentSchemas: + chunk: + not: + anyOf: + - required: + - exited + - required: + - heartbeat + exited: + not: + anyOf: + - required: + - chunk + - required: + - heartbeat + heartbeat: + not: + anyOf: + - required: + - chunk + - required: + - exited + Process: + type: object + properties: + uid: + type: + - string + - "null" + description: (IMMUTABLE) uid is the existing process incarnation, also used as the resource name terminal. + readOnly: true + pid: + type: integer + format: int32 + description: pid is the process id inside the sandbox when available. + session: + type: string + description: session is the session tag from the create, empty when untagged. + state: + description: state is the process state, concrete in conforming responses. + $ref: "#/components/schemas/ProcessState" + lastStreamSequence: + type: + - integer + - string + format: int64 + description: last_stream_sequence is the highest stream_sequence emitted for the process when this report was assembled; 0 before any output. + receiptKind: + description: |- + receipt_kind classifies this response on CreateProcess; every other operation + returning a Process, including ListProcesses, always reports unspecified. + $ref: "#/components/schemas/ReceiptKind" + exitCode: + type: + - integer + - "null" + format: int32 + description: exit_code is the process exit code, set only when state is exited and absent otherwise. + label: + type: string + minLength: 1 + description: (IMMUTABLE) label retains the unique, immutable process label and its generated default. + name: + type: string + description: (IDENTIFIER) name is scoped to the discovered sandbox endpoint API base. + title: Process + required: + - label + description: Process is the handle returned before interaction begins. + ProcessState: + type: string + title: ProcessState + enum: + - unspecified + - running + - exited + description: |- + ProcessState is the coarse process lifecycle a process report carries. + + Values: + unspecified: unspecified is not a concrete state; conforming responses never use it. + running: running means the process has not exited. + exited: exited means the process has exited. + PtyConfig: + type: object + properties: + initialSize: + description: initial_size requests an initial terminal size; absence selects a backend-defined size. + $ref: "#/components/schemas/TerminalSize" + title: PtyConfig + additionalProperties: false + description: PtyConfig configures pseudo-terminal creation. + ReadOutputResponse: + type: object + properties: + chunks: + type: array + items: + $ref: "#/components/schemas/ProcessChunk" + description: chunks is retained output in stream_sequence order, possibly empty. + nextResumeFrom: + type: + - integer + - string + format: int64 + description: |- + next_resume_from is the stream_sequence to pass next; equal to resume_from + when nothing new was retained. + exited: + description: exited is set once the process has exited and no retained bytes follow next_resume_from. + $ref: "#/components/schemas/ProcessExited" + title: ReadOutputResponse + description: ReadOutputResponse is one page of ordered output. + ReceiptKind: + type: string + title: ReceiptKind + enum: + - unspecified + - fresh + - replay + description: |- + ReceiptKind classifies whether a CreateProcess response was freshly assembled or + is the retained receipt of an earlier accepted request. + + Values: + unspecified: unspecified is not a concrete classification; a backend or a client generated before this field existed reports or reads this. It asserts neither freshness nor replay. + fresh: fresh means this Process was assembled for this specific accepted request; last_stream_sequence reflects live state as of that acceptance. + replay: replay means the backend returned the retained result of an earlier accepted request under the synchronous mutation replay rule; last_stream_sequence is frozen at the value observed during that original acceptance. + ProcessSignal: + type: string + title: Signal + enum: + - unspecified + - term + - kill + - int + - hup + - quit + - usr1 + - usr2 + description: |- + Signal is a process signal. + + Values: + unspecified: unspecified is invalid. + term: term requests graceful termination. + kill: kill requests immediate termination. + int: int requests interrupt. + hup: hup requests hangup. + quit: quit requests quit. + usr1: usr1 requests user signal 1. + usr2: usr2 requests user signal 2. + SignalResponse: + type: object + title: SignalResponse + description: SignalResponse has no fields. + StreamType: + type: string + title: StreamType + enum: + - unspecified + - stdout + - stderr + - pty + description: |- + StreamType identifies the source of an output chunk. + + Values: + unspecified: unspecified is not a concrete output stream. + stdout: stdout is stdout. + stderr: stderr is stderr. + pty: pty is merged PTY output. + TerminalSize: + type: object + properties: + rows: + type: integer + description: rows is the terminal row count. + cols: + type: integer + description: cols is the terminal column count. + bounded: + type: boolean + description: |- + bounded requests the portable range enforced on this size, with no opt in + to perform first. Unset or false keeps the unenforced behavior. + title: TerminalSize + additionalProperties: false + description: |- + TerminalSize is a terminal rows/columns pair; 1 through 65535 is the portable range. + bounded requires rows and cols in 1..65535 + AddMcpGatewayServerResponse: + type: object + properties: + server: + description: server is the gateway server. + $ref: "#/components/schemas/McpServer" + title: AddMcpGatewayServerResponse + description: AddMcpGatewayServerResponse returns the added or already-present server. + AttributedRule: + type: object + properties: + network: + type: string + description: network is the network name or CIDR. + layer: + description: layer identifies where the rule came from. + $ref: "#/components/schemas/PolicyLayer" + title: AttributedRule + description: AttributedRule reports a rule and the layer that supplied it. + CaptureMode: + type: string + title: CaptureMode + enum: + - unspecified + - all + - disk + description: |- + CaptureMode selects whether captured images include memory. + + Values: + unspecified: unspecified means disk-only capture. + all: all captures memory and disk and requires memory-capture support. + disk: disk captures disk only. + CreateImageRequest: + type: object + allOf: + - properties: + displayName: + type: string + not: + type: string + enum: + - . + - .. + minLength: 1 + pattern: ^[^/]+$ + description: display_name is a scoped label; the backend assigns the immutable resource ID. + description: + type: string + description: description is caller-supplied text. + startCmd: + type: array + items: + type: string + description: start_cmd is used when the image is launched as a raw image sandbox. + readyCmd: + type: array + items: + type: string + description: ready_cmd is used to determine readiness for raw image sandboxes. + - oneOf: + - type: object + properties: + fromImage: + description: from_image registers an entry that will receive pushed OCI content. + $ref: "#/components/schemas/ImageFromImage" + title: from_image + required: + - fromImage + title: CreateImageRequest + required: + - displayName + unevaluatedProperties: false + description: CreateImageRequest creates a managed image. + CreatePortRequestPortInput: + type: object + properties: + number: + type: integer + maximum: 65535 + minimum: 1 + format: int32 + description: (IMMUTABLE) number is immutable and unique within the sandbox; duplicates refuse ALREADY_EXISTS. + protocol: + type: string + title: PortInputProtocol + enum: + - unspecified + - tcp + description: (IMMUTABLE) protocol defaults to TCP when unspecified. + title: PortInput + required: + - number + additionalProperties: false + CreateSandboxRequest: + type: object + allOf: + - properties: + agent: + type: string + description: agent names an optional agent profile; unsupported profiles fail explicitly. + displayName: + type: string + maxLength: 64 + pattern: ^[a-zA-Z0-9_-]*$ + description: (OPTIONAL) display_name is a scoped label; the backend assigns the immutable resource ID. + displayNamePrefix: + type: string + description: (OPTIONAL) display_name_prefix requests a generated label and excludes display_name. + environment: + type: object + additionalProperties: + type: string + title: value + description: environment is non-secret environment metadata. + resources: + description: resources requests CPU and memory within the selected placement's limits. + $ref: "#/components/schemas/Resources" + pullPolicy: + type: string + title: CreateSandboxRequestPullPolicy + enum: + - unspecified + - always + description: pull_policy retains shared image-pull semantics and explicit support errors. + kits: + type: array + items: + $ref: "#/components/schemas/KitInput" + description: kits are the kit inputs to apply before readiness, in composition order. + mcp: + description: mcp requests an MCP gateway wired before the workload starts. + $ref: "#/components/schemas/McpCreateSpec" + networkPolicies: + type: array + items: + $ref: "#/components/schemas/NetworkPolicy" + description: |- + network_policies are inline policies materialized in governance and enforced before the first guest process. + Omission or an empty list requests no inline policies; unsupported policies fail before create effects. + policyIds: + type: array + items: + type: string + minLength: 1 + description: policy_ids names existing governance policies, never raw policy content. + platform: + description: |- + platform selects an exact supported, image-compatible tuple when present. + explicit platform requires OS and architecture + $ref: "#/components/schemas/Platform" + startupExecution: + description: |- + startup_execution is ordinary create input and persists for + the sandbox lifetime. + $ref: "#/components/schemas/StartupExecution" + labels: + type: object + maxProperties: 64 + additionalProperties: + type: string + title: value + description: "" + description: |- + labels are non-secret caller-assigned key/value metadata recorded at admission. + This contract has no verb that changes labels afterwards. + features: + description: features carries typed inputs validated before admission can have effects. + $ref: "#/components/schemas/FeatureOptions" + - properties: + image: + type: string + pattern: ^images/[^/]+$ + description: image is the managed Image resource name. + imageRef: + type: string + description: image_ref is an external OCI reference, with the existing backend validation. + dependentSchemas: + image: + not: + anyOf: + - required: + - imageRef + imageRef: + not: + anyOf: + - required: + - image + title: CreateSandboxRequest + unevaluatedProperties: false + description: |- + CreateSandboxRequest validates typed composition intent before replay and + persists accepted guarantees, distinct enough from legacy create that old + servers cannot silently drop it. + display_name and display_name_prefix are mutually exclusive At most one of image, imageRef is set. + CreateSecretRequest: + type: object + allOf: + - properties: + displayName: + type: string + not: + type: string + enum: + - . + - .. + minLength: 1 + pattern: ^[^/]+$ + description: display_name is a scoped label; the backend assigns the immutable resource ID. + serviceType: + type: string + description: service_type identifies the consuming service. + - oneOf: + - type: object + properties: + custom: + description: |- + custom stores an opaque value with caller-authored injection metadata. + CreateSecretRequest.service_type is server-assigned for custom + material; leave it empty. + $ref: "#/components/schemas/CustomSecretMaterial" + title: custom + required: + - custom + - type: object + properties: + oauth: + description: oauth stores OAuth refresh material. + $ref: "#/components/schemas/OAuthRefreshMaterial" + title: oauth + required: + - oauth + - type: object + properties: + token: + description: token stores opaque token material. + $ref: "#/components/schemas/TokenSecretMaterial" + title: token + required: + - token + title: CreateSecretRequest + required: + - displayName + unevaluatedProperties: false + description: |- + CreateSecretRequest stores new secret material. + service_type must be empty for custom material (server-assigned) + CreateVolumeRequest: + type: object + properties: + displayName: + type: string + not: + type: string + enum: + - . + - .. + minLength: 1 + pattern: ^[^/]+$ + description: display_name is a scoped label; the backend assigns the immutable resource ID. + title: CreateVolumeRequest + required: + - displayName + additionalProperties: false + description: CreateVolumeRequest creates one volume. + CredentialFenceReceipt: + type: object + properties: + target: + type: string + pattern: ^(sandboxes|secrets)/[^/]+$ + description: target is the complete Sandbox or Secret resource name bound to this receipt. + generation: + exclusiveMinimum: 0 + type: + - integer + - string + format: int64 + description: generation increases for each accepted material/rule replacement on that target. + title: CredentialFenceReceipt + required: + - target + - generation + description: CredentialFenceReceipt acknowledges one completed generation without granting current use. + CustomInjection: + type: object + properties: + hosts: + type: array + items: + type: string + minLength: 1 + maxItems: 10 + minItems: 1 + description: |- + hosts are exact lowercase DNS names. Wildcards, IP literals, ports, + schemes, paths, and hosts a managed integration already serves are + rejected. + header: + type: string + minLength: 1 + description: |- + header is the HTTP header the value is written to. Routing, framing, + and cookie headers are rejected. + format: + type: string + description: |- + format renders the value with exactly one %s; %% escapes a literal + percent, and any other % sequence is rejected. Empty means "%s". + envVar: + type: string + description: |- + env_var optionally names a sandbox environment variable that carries the + secret's placeholder (never the value) as a delivery convenience. + placeholderTemplate: + type: string + description: |- + placeholder_template optionally shapes the placeholder for clients that + validate credential shape before sending (e.g. "sk-corp-{rand}"). Each + {rand} token is expanded server-side; {rand} is the only token. Empty + uses the backend default ("sbx-cs-{rand}"). + title: CustomInjection + required: + - hosts + - header + additionalProperties: false + description: |- + CustomInjection is caller-authored, non-secret metadata for a custom + secret: which exact hosts receive the value, in which header and format, + and how the placeholder is shaped and delivered. Unlike secret material, + reads return it. + CustomSecretMaterial: + type: object + properties: + value: + type: string + minLength: 1 + description: value is the secret value. + writeOnly: true + injection: + description: injection is the caller-authored routing. + $ref: "#/components/schemas/CustomInjection" + title: CustomSecretMaterial + required: + - value + - injection + additionalProperties: false + description: CustomSecretMaterial is an opaque secret value plus its injection routing. + DeleteSecretResponse: + type: object + properties: + credentialFence: + description: credential_fence remains the original historical receipt after deletion or name reuse. + $ref: "#/components/schemas/CredentialFenceReceipt" + title: DeleteSecretResponse + description: DeleteSecretResponse acknowledges deletion and optional consumer fencing. + EffectiveNetworkPolicy: + type: object + properties: + mode: + not: + enum: + - unspecified + description: mode is the effective mode and is always concrete in conforming responses. + $ref: "#/components/schemas/NetworkPolicyMode" + allowNetworks: + type: array + items: + $ref: "#/components/schemas/AttributedRule" + description: allow_networks are effective allowed rules with attribution. + denyNetworks: + type: array + items: + $ref: "#/components/schemas/AttributedRule" + description: deny_networks are effective denied rules with attribution. + title: EffectiveNetworkPolicy + required: + - mode + description: EffectiveNetworkPolicy is the policy after layering and fail-closed defaults. + EndpointCredential: + type: object + properties: + token: + type: string + minLength: 1 + expireTime: + $ref: "#/components/schemas/Timestamp" + permissions: + type: array + items: + $ref: "#/components/schemas/Permission" + enum: + - sandboxesExec + - sandboxesFilesRead + - sandboxesFilesWrite + minItems: 1 + uniqueItems: true + description: "Allowed values: sandboxesExec, sandboxesFilesRead, sandboxesFilesWrite." + sandbox: + type: string + pattern: ^sandboxes/[^/]+$ + audience: + type: string + minLength: 1 + title: EndpointCredential + required: + - token + - expireTime + - permissions + - sandbox + - audience + description: "EndpointCredential is returned once with Cache-Control: no-store; never log its token." + EtagMismatch: + type: object + properties: + currentEtag: + type: string + minLength: 1 + description: current_etag is informational; clients must not silently retry with it. + title: EtagMismatch + required: + - currentEtag + description: |- + EtagMismatch is the status detail a stale precondition carries, so a client learns the + current version for an explicit conflict-resolution decision. + EtagMismatchErrorDetail: + type: object + properties: + currentEtag: + type: string + minLength: 1 + description: current_etag is informational; clients must not silently retry with it. + "@type": + type: string + const: type.googleapis.com/docker.sandboxes.v1.EtagMismatch + title: EtagMismatchErrorDetail + required: + - currentEtag + - "@type" + description: |- + EtagMismatch is the status detail a stale precondition carries, so a client learns the + current version for an explicit conflict-resolution decision. + ExactDestination: + type: object + properties: + kind: + not: + enum: + - unspecified + description: kind selects a destination.v1 matching rule. + $ref: "#/components/schemas/ExactDestinationKind" + value: + type: string + minLength: 1 + description: value is canonical for its kind and contains no legacy selector syntax. + port: + type: + - integer + - "null" + maximum: 65535 + minimum: 1 + description: An absent port matches all numeric ports; zero is invalid when present. + title: ExactDestination + required: + - kind + - value + description: ExactDestination uses destination.v1 canonical selector values, never legacy glob syntax. + ExactDestinationKind: + type: string + title: ExactDestinationKind + enum: + - unspecified + - dns + - dnsLabel + - dnsSubtree + - ip + - cidr + description: |- + ExactDestinationKind keeps DNS and IP-literal decisions distinct. + + Values: + unspecified + dns + dnsLabel + dnsSubtree + ip + cidr + ExactNetworkPolicy: + type: object + properties: + revision: + type: string + minLength: 1 + description: revision binds authority, configuration and resolved conditions to the installed generation. + selectorVersion: + type: string + description: selector_version fixes matching semantics across implementations. + const: destination.v1 + nodes: + type: array + items: + $ref: "#/components/schemas/ExactPolicyNode" + maxItems: 256 + minItems: 1 + description: nodes are complete and topologically ordered; the final node is the root. + title: ExactNetworkPolicy + required: + - revision + - selectorVersion + - nodes + description: ExactNetworkPolicy is a complete consumer-installed generation; its last node is the root. + ExactPolicyConstant: + type: string + title: ExactPolicyConstant + enum: + - unspecified + - "true" + - "false" + description: |- + ExactPolicyConstant makes both Boolean identities explicit. + + Values: + unspecified + true + false + ExactPolicyNode: + type: object + allOf: + - properties: + source: + description: source is required on constants and destinations and absent on Boolean operators. + $ref: "#/components/schemas/ExactPolicySource" + - oneOf: + - type: object + properties: + all: + description: all intersects every operand. + $ref: "#/components/schemas/ExactPolicyOperands" + title: all + required: + - all + - type: object + properties: + any: + description: any unions its operands without discarding enclosing ceilings. + $ref: "#/components/schemas/ExactPolicyOperands" + title: any + required: + - any + - type: object + properties: + constant: + not: + enum: + - unspecified + description: constant explicitly allows or denies independently of the destination. + $ref: "#/components/schemas/ExactPolicyConstant" + title: constant + required: + - constant + - type: object + properties: + destination: + description: destination matches only its declared DNS or IP kind. + $ref: "#/components/schemas/ExactDestination" + title: destination + required: + - destination + - type: object + properties: + not: + type: integer + description: not negates one earlier node. + title: not + required: + - not + title: ExactPolicyNode + description: ExactPolicyNode is topologically ordered; references must precede this node. + ExactPolicyOperands: + type: object + properties: + nodes: + type: array + items: + type: integer + maxItems: 64 + minItems: 1 + description: nodes must all precede the enclosing operator. + title: ExactPolicyOperands + required: + - nodes + description: ExactPolicyOperands contains earlier node indexes; empty Boolean groups are invalid. + ExactPolicyOrigin: + type: string + title: ExactPolicyOrigin + enum: + - unspecified + - organization + - owner + - attached + - kit + - gateway + - default + description: |- + ExactPolicyOrigin separates governance from attached and runtime-owned permissions. + + Values: + unspecified + organization + owner + attached + kit + gateway + default + ExactPolicySource: + type: object + properties: + origin: + not: + enum: + - unspecified + description: origin distinguishes the authority that supplied this leaf. + $ref: "#/components/schemas/ExactPolicyOrigin" + id: + type: string + minLength: 1 + description: id identifies that source without exposing credentials or policy content. + title: ExactPolicySource + required: + - origin + - id + description: ExactPolicySource identifies the nonsecret authority of a leaf expression. + ExchangeCompositionCredentialResponse: + type: object + title: ExchangeCompositionCredentialResponse + GetNetworkPoliciesResponse: + type: object + properties: + effective: + $ref: "#/components/schemas/EffectiveNetworkPolicy" + exact: + $ref: "#/components/schemas/ExactNetworkPolicy" + title: GetNetworkPoliciesResponse + required: + - effective + - exact + description: GetNetworkPoliciesResponse binds both views to exact.revision; a partial read fails. + IdentityExchangeServiceExchangeDockerCredentialRequest: + type: object + properties: + idToken: + type: string + minLength: 1 + description: id_token is single-use identity material, never an owner selector. + writeOnly: true + title: IdentityExchangeServiceExchangeDockerCredentialRequest + required: + - idToken + additionalProperties: false + Image: + type: object + properties: + uid: + type: + - string + minLength: 1 + description: (IMMUTABLE) uid is the existing backing ID, equal to the resource name's terminal segment. + readOnly: true + name: + type: string + pattern: ^images/[^/]+$ + description: (IDENTIFIER) name ends in the immutable backing ID and never changes with display_name. + description: + type: string + description: description is caller-supplied text. + status: + not: + enum: + - unspecified + description: status is the image readiness state. + readOnly: true + $ref: "#/components/schemas/ImageStatus" + imageRef: + type: string + description: image_ref is the registry or backend image reference when available. + imageDigest: + type: string + description: image_digest is the content digest when known. + resources: + description: resources describes the image's preferred runtime resources. + $ref: "#/components/schemas/Resources" + startCmd: + type: array + items: + type: string + description: start_cmd is the command used by raw image sandboxes. + readyCmd: + type: array + items: + type: string + description: ready_cmd is the readiness command used by raw image sandboxes. + platform: + description: platform is the image platform. + $ref: "#/components/schemas/Platform" + totalSizeBytes: + type: + - integer + - string + format: int64 + description: total_size_bytes is the approximate stored image size. + createdAt: + description: created_at is the creation timestamp. + $ref: "#/components/schemas/Timestamp" + updatedAt: + description: updated_at is the last update timestamp. + $ref: "#/components/schemas/Timestamp" + failure: + description: failure is set when status is FAILED. + $ref: "#/components/schemas/Error" + source: + description: source records how the image was created. + $ref: "#/components/schemas/ImageSource" + captureMode: + description: capture_mode records whether captured content includes memory. + $ref: "#/components/schemas/CaptureMode" + etag: + type: string + minLength: 1 + pattern: ^"[^"\x00-\x20\x7f]*"$ + description: |- + etag identifies the observed version of this resource. It is opaque and strong, + changes on every visible change, and is what a mutation sends as its precondition. + readOnly: true + pushTarget: + description: push_target appears only in from_image create responses awaiting upload, never reads. + readOnly: true + $ref: "#/components/schemas/ImagePushTarget" + displayName: + type: string + description: display_name is a scoped label and never selects the resource. + title: Image + required: + - uid + - name + - status + - createdAt + - updatedAt + - etag + description: |- + Image is the detailed managed image view. + uid must equal the immutable resource ID in name + ImageBlobRef: + type: object + properties: + digest: + type: string + description: digest is the content digest. + mediaType: + type: string + description: media_type is the OCI media type. + size: + type: + - integer + - string + format: int64 + description: size is the blob size in bytes. + url: + type: string + description: url is a short-lived pull URL. + title: ImageBlobRef + description: ImageBlobRef describes one pullable blob. + ImageFromImage: + type: object + properties: + resources: + description: resources describes preferred runtime resources for the resulting image. + $ref: "#/components/schemas/Resources" + platform: + description: platform is the platform for the pushed content. + $ref: "#/components/schemas/Platform" + captureMode: + type: string + title: ImageFromImageCaptureMode + enum: + - unspecified + - disk + description: capture_mode must be unspecified or disk; pushed OCI content cannot include memory. + not: + enum: + - all + platformHint: + description: |- + platform_hint picks among a multi-platform push when platform is absent. + A hint the backend cannot honor is ignored, never an error. + $ref: "#/components/schemas/Platform" + kitStartup: + description: Startup commands copied from the source sandbox. Services with startup replay run these commands when a sandbox created from the image boots. Services without startup replay accept valid commands without running them. + $ref: "#/components/schemas/KitStartup" + title: ImageFromImage + additionalProperties: false + description: ImageFromImage registers a target for pushed OCI content. + ImagePullSpec: + type: object + properties: + manifestDigest: + type: string + description: manifest_digest is the manifest digest. + manifestMediaType: + type: string + description: manifest_media_type is the manifest media type. + config: + description: config is the image config blob. + $ref: "#/components/schemas/ImageBlobRef" + layers: + type: array + items: + $ref: "#/components/schemas/ImageBlobRef" + description: layers are the image layer blobs. + expiresAt: + description: expires_at is when the pull material expires. + $ref: "#/components/schemas/Timestamp" + title: ImagePullSpec + description: ImagePullSpec describes short-lived pull material for placement. + ImagePushTarget: + type: object + properties: + imageRef: + type: string + description: image_ref is the registry reference to push. + accessToken: + type: string + description: access_token is the short-lived push credential. + accessTokenExpiresAt: + description: access_token_expires_at is the token expiry. + $ref: "#/components/schemas/Timestamp" + title: ImagePushTarget + description: ImagePushTarget carries short-lived upload credentials. + ImageSource: + type: string + title: ImageSource + enum: + - unspecified + - image + - sandbox + description: |- + ImageSource records how the image was created. + + Values: + unspecified: unspecified means the source is unknown or not reported. + image: image means the image came from pushed or imported image bytes. + sandbox: sandbox means the image came from a sandbox capture. + ImageStatus: + type: string + title: ImageStatus + enum: + - unspecified + - pending + - waitingForPush + - preparing + - completed + - failed + - deleted + description: |- + ImageStatus is the image readiness state. + + Values: + unspecified: unspecified is never a concrete known state. + pending: pending means an asynchronous build has not started or reported progress. + waitingForPush: waitingForPush means the backend is waiting for OCI content upload. + preparing: preparing means the backend is preparing pushed or captured content. + completed: completed means the image is ready to use. + failed: failed means image preparation reached a terminal failure. + deleted: deleted means the backend exposes a retained delete tombstone. + ImageSummary: + type: object + properties: + uid: + type: + - string + minLength: 1 + description: (IMMUTABLE) uid is the existing backing ID, equal to the resource name's terminal segment. + readOnly: true + name: + type: string + pattern: ^images/[^/]+$ + description: name ends in the immutable backing ID and never changes with display_name. + status: + not: + enum: + - unspecified + description: status is the image readiness state. + $ref: "#/components/schemas/ImageStatus" + source: + description: source records how the image was created. + $ref: "#/components/schemas/ImageSource" + totalSizeBytes: + type: + - integer + - string + format: int64 + description: total_size_bytes is the approximate stored image size. + createdAt: + description: created_at is the creation timestamp. + $ref: "#/components/schemas/Timestamp" + updatedAt: + description: updated_at is the last update timestamp. + $ref: "#/components/schemas/Timestamp" + imageRef: + type: string + description: image_ref is the registry or backend image reference when available. + imageDigest: + type: string + description: image_digest is the content digest when known. + captureMode: + description: capture_mode records whether captured content includes memory. + $ref: "#/components/schemas/CaptureMode" + platform: + description: platform is the image platform. + $ref: "#/components/schemas/Platform" + displayName: + type: string + description: display_name is a scoped label and never selects the resource. + title: ImageSummary + required: + - uid + - name + - status + - createdAt + - updatedAt + description: |- + ImageSummary is the list view of an image. + uid must equal the immutable resource ID in name + IssueSSHCertResponse: + type: object + properties: + certificate: + type: string + description: certificate is the signed SSH certificate. + sshConfig: + type: string + description: |- + ssh_config optionally renders the connection fields as OpenSSH client + configuration; the structured fields are canonical. + knownHosts: + type: string + description: known_hosts is host key material that authenticates the SSH endpoint. + expiresAt: + description: expires_at is the instant after which the certificate is invalid. + $ref: "#/components/schemas/Timestamp" + host: + type: string + description: host is the SSH endpoint host to connect to. + port: + type: integer + description: port is the SSH endpoint port to connect to. + username: + type: string + description: username is the SSH username to present; it carries no authorization. + title: IssueSSHCertResponse + description: IssueSSHCertResponse returns signed SSH client material, never a private key. + KitArtifactInput: + type: object + allOf: + - properties: + ref: + $ref: "#/components/schemas/KitRef" + - oneOf: + - type: object + properties: + inline: + type: string + format: byte + description: inline is the artifact in the pinned JSON encoding. + title: inline + required: + - inline + title: KitArtifactInput + required: + - ref + unevaluatedProperties: false + description: KitArtifactInput carries a v2 artifact inline or by staged digest. + KitFieldDisposition: + type: string + title: KitFieldDisposition + enum: + - unspecified + - skipped + - refused + description: |- + KitFieldDisposition describes a declaration that was not applied. + + Values: + unspecified: unspecified is invalid in a report. + skipped: skipped identifies a contract-permitted optional skip. + refused: refused prevents guest or kit effects. + KitFieldOutcome: + type: object + properties: + kitIndex: + exclusiveMaximum: 64 + type: integer + description: kit_index is the zero-based position in the originating request's kits. + ref: + type: string + description: ref preserves the exact requested kit reference. + digest: + type: string + description: digest is the resolved content identity when resolution succeeded. + fieldPath: + type: string + description: field_path is an RFC 6901 pointer into the pinned JSON artifact. + disposition: + not: + enum: + - unspecified + description: disposition never claims that an accepted declaration has executed. + $ref: "#/components/schemas/KitFieldDisposition" + reason: + not: + enum: + - unspecified + description: reason is a bounded category, not caller-controlled diagnostic content. + $ref: "#/components/schemas/KitFieldReason" + reasonText: + type: string + description: reason_text contains nonsecret explanatory text, never values or command content. + title: KitFieldOutcome + required: + - ref + - fieldPath + - disposition + - reason + description: KitFieldOutcome identifies one non-applied declaration without exposing its value. + KitFieldOutcomeReport: + type: object + properties: + complete: + type: boolean + description: complete means every refused or skipped declaration is accounted for; absence proves nothing. + outcomes: + type: array + items: + $ref: "#/components/schemas/KitFieldOutcome" + maxItems: 256 + description: outcomes follow request index then UTF-8 field-path order and are never truncated. + title: KitFieldOutcomeReport + description: KitFieldOutcomeReport records settled diagnostics on a sandbox or an admission refusal. + KitFieldOutcomeReportErrorDetail: + type: object + properties: + complete: + type: boolean + description: complete means every refused or skipped declaration is accounted for; absence proves nothing. + outcomes: + type: array + items: + $ref: "#/components/schemas/KitFieldOutcome" + maxItems: 256 + description: outcomes follow request index then UTF-8 field-path order and are never truncated. + "@type": + type: string + const: type.googleapis.com/docker.sandboxes.v1.KitFieldOutcomeReport + title: KitFieldOutcomeReportErrorDetail + description: KitFieldOutcomeReport records settled diagnostics on a sandbox or an admission refusal. + required: + - "@type" + KitFieldReason: + type: string + title: KitFieldReason + enum: + - unspecified + - unsupported + - unknownField + - credentialUnavailable + description: |- + KitFieldReason provides stable, nonsecret diagnostic categories. + + Values: + unspecified: unspecified is invalid in a report. + unsupported: unsupported means the backend cannot honor the declaration. + unknownField: unknownField means the pinned schema does not recognize the declaration. + credentialUnavailable: credentialUnavailable means the declared credential cannot be injected. + KitInput: + type: object + oneOf: + - type: object + properties: + artifact: + $ref: "#/components/schemas/KitArtifactInput" + title: artifact + required: + - artifact + title: KitInput + unevaluatedProperties: false + description: KitInput is one kit in either shape. + KitStartup: + type: object + properties: + kits: + type: array + items: + type: string + minLength: 1 + minItems: 1 + description: |- + kits names the kit identities the commands derive from, in composition + order. Required. + commands: + type: array + items: + $ref: "#/components/schemas/KitStartupCommand" + minItems: 1 + description: commands run in order at sandbox boot, on a backend that replays them. + title: KitStartup + required: + - kits + - commands + additionalProperties: false + description: Startup commands retained when creating an image. Services with startup replay run them at each boot; services without it accept valid commands without running them. + KitStartupCommand: + type: object + properties: + argv: + type: array + items: + type: string + minItems: 1 + description: |- + argv is the command and its arguments, exec-style. An argument other than + the executable itself may be empty (an intentional empty string, as in + `printf %s ""`); only the vector itself is required non-empty. + user: + type: string + description: user runs the command as this user; empty means the backend default. + background: + type: boolean + description: background launches the command detached instead of waiting for it. + title: KitStartupCommand + required: + - argv + additionalProperties: false + description: |- + KitStartupCommand is one startup command in a KitStartup set, run in order + by a backend that replays it at sandbox boot. + ListImagesResponse: + type: object + properties: + images: + type: array + items: + $ref: "#/components/schemas/ImageSummary" + description: images is the current page. + nextPageToken: + type: string + description: next_page_token is empty when there are no more pages. + title: ListImagesResponse + description: ListImagesResponse returns image results and a next-page token. + ListPolicyLogEntriesResponse: + type: object + properties: + entries: + type: array + items: + $ref: "#/components/schemas/PolicyLogEntry" + description: entries is the current page. + nextPageToken: + type: string + description: next_page_token is empty when there are no more pages. + title: ListPolicyLogEntriesResponse + description: ListPolicyLogEntriesResponse returns policy log entries and a next-page token. + ListPortsResponse: + type: object + properties: + published: + type: array + items: + $ref: "#/components/schemas/Port" + description: published is ordered by number ascending. + title: ListPortsResponse + description: ListPortsResponse returns currently published ports. + ListSandboxesResponse: + type: object + properties: + sandboxes: + type: array + items: + $ref: "#/components/schemas/Sandbox" + description: sandboxes is the current page. + nextPageToken: + type: string + description: next_page_token is empty when there are no more pages. + title: ListSandboxesResponse + description: ListSandboxesResponse returns sandbox results and a next-page token. + ListSecretsResponse: + type: object + properties: + secrets: + type: array + items: + $ref: "#/components/schemas/Secret" + description: secrets is the current page. + nextPageToken: + type: string + description: next_page_token is empty when there are no more pages. + title: ListSecretsResponse + description: ListSecretsResponse returns secrets and a next-page token. + ListSnapshotsResponse: + type: object + properties: + snapshots: + type: array + items: + $ref: "#/components/schemas/SnapshotSummary" + description: snapshots is the current page. + nextPageToken: + type: string + description: next_page_token is empty when there are no more pages. + title: ListSnapshotsResponse + description: ListSnapshotsResponse returns snapshot results and a next-page token. + ListVolumesResponse: + type: object + properties: + volumes: + type: array + items: + $ref: "#/components/schemas/Volume" + description: volumes is the current page. + nextPageToken: + type: string + description: next_page_token is empty when there are no more pages. + title: ListVolumesResponse + description: ListVolumesResponse returns volumes and a next-page token. + McpAuthorization: + type: object + properties: + server: + type: string + pattern: ^(?:mcp-servers/[^/]+)?$ + description: (OPTIONAL) server is the complete resource name, in the form mcp-servers/{server}. + uid: + type: + - string + - "null" + description: (IMMUTABLE) uid is the existing immutable backing identity, omitted when none is available. + readOnly: true + status: + readOnly: true + $ref: "#/components/schemas/McpAuthorizationStatus" + authorizationUrl: + type: string + description: authorization_url is present only while interaction is pending. + readOnly: true + createdAt: + readOnly: true + $ref: "#/components/schemas/Timestamp" + updatedAt: + readOnly: true + $ref: "#/components/schemas/Timestamp" + expiresAt: + description: expires_at bounds the pending flow; expiry records FAILED on this incarnation. + readOnly: true + $ref: "#/components/schemas/Timestamp" + failure: + readOnly: true + $ref: "#/components/schemas/Error" + etag: + type: string + description: etag identifies this incarnation and changes with every visible authorization update. + readOnly: true + name: + type: string + description: (IDENTIFIER) name is the complete singleton resource name. + title: McpAuthorization + description: McpAuthorization is the current authorization incarnation for one named upstream. + McpAuthorizationStatus: + type: string + title: McpAuthorizationStatus + enum: + - unspecified + - pending + - authorized + - failed + description: |- + McpAuthorizationStatus reports the current upstream authorization outcome. + + Values: + unspecified + pending + authorized + failed + McpConfigurationMode: + type: string + title: McpConfigurationMode + enum: + - unspecified + - dynamic + - static + - attached + description: |- + McpConfigurationMode identifies desired hosting ownership and discovery behavior. + + Values: + unspecified: unspecified is not a concrete report mode. + dynamic: dynamic uses a backend-minted gateway with discovery. + static: static uses a backend-minted gateway without discovery. + attached: attached retains a read-only external binding. + McpConfigurationOutcome: + type: string + title: McpConfigurationOutcome + enum: + - unspecified + - pending + - applied + - skipped + - failed + description: |- + McpConfigurationOutcome describes one requested server in the reported generation. + + Values: + unspecified: unspecified is not a concrete outcome. + pending: pending has not settled and cannot establish completeness. + applied: applied is confirmed membership, not upstream authorization. + skipped: skipped requires an explicitly applicable compatibility rule. + failed: failed retains intent that could not be provisioned. + McpConfigurationReport: + type: object + properties: + generation: + type: string + description: generation is an opaque durable fence, never ordered or synthesized by callers. + mode: + not: + enum: + - unspecified + description: mode separates minted discovery behavior from read-only attachment. + $ref: "#/components/schemas/McpConfigurationMode" + complete: + type: boolean + description: complete means every active requested server is applied or explicitly permitted to skip. + servers: + type: array + items: + $ref: "#/components/schemas/McpConfiguredServer" + maxItems: 256 + description: servers is the complete active requested set; together with dormant_servers it matches McpGateway.servers. + dormantServers: + type: array + items: + $ref: "#/components/schemas/McpServer" + maxItems: 256 + description: dormant_servers retains earlier minted intent while attached; it is empty in minted modes. + title: McpConfigurationReport + required: + - generation + - mode + description: McpConfigurationReport is also returned in Error.details on accepted provisioning failure. + McpConfigurationReportErrorDetail: + type: object + properties: + generation: + type: string + description: generation is an opaque durable fence, never ordered or synthesized by callers. + mode: + not: + enum: + - unspecified + description: mode separates minted discovery behavior from read-only attachment. + $ref: "#/components/schemas/McpConfigurationMode" + complete: + type: boolean + description: complete means every active requested server is applied or explicitly permitted to skip. + servers: + type: array + items: + $ref: "#/components/schemas/McpConfiguredServer" + maxItems: 256 + description: servers is the complete active requested set; together with dormant_servers it matches McpGateway.servers. + dormantServers: + type: array + items: + $ref: "#/components/schemas/McpServer" + maxItems: 256 + description: dormant_servers retains earlier minted intent while attached; it is empty in minted modes. + "@type": + type: string + const: type.googleapis.com/docker.sandboxes.v1.McpConfigurationReport + title: McpConfigurationReportErrorDetail + required: + - generation + - mode + - "@type" + description: McpConfigurationReport is also returned in Error.details on accepted provisioning failure. + McpConfiguredServer: + type: object + properties: + server: + description: server is the resolved requested server; its type cannot silently change within retained intent. + $ref: "#/components/schemas/McpServer" + outcome: + not: + enum: + - unspecified + description: outcome is concrete even when the gateway remains ready after a failed add. + $ref: "#/components/schemas/McpConfigurationOutcome" + reason: + type: string + description: reason is nonsecret context and never carries credentials or credential-bearing URLs. + title: McpConfiguredServer + required: + - server + - outcome + description: McpConfiguredServer preserves one canonical requested identity and its provisioning outcome. + McpCreateSpec: + type: object + properties: + servers: + type: array + items: + type: string + minLength: 1 + description: servers are initial server names for a backend-minted gateway. + static: + type: boolean + description: static pins the requested server set and closes gateway-side discovery. + gatewayUrl: + type: + - string + - "null" + format: uri + description: gateway_url attaches read-only to a pre-existing shareable gateway when supported. + title: McpCreateSpec + additionalProperties: false + description: |- + McpCreateSpec is CreateSandboxRequest's MCP block: the StartMcpGateway + fields without the sandbox ref, wired before the workload starts. + servers must be empty when gateway_url is set + McpGateway: + type: object + properties: + url: + type: string + description: url is the gateway endpoint. + state: + description: state is the gateway readiness state. + $ref: "#/components/schemas/McpGatewayState" + servers: + type: array + items: + $ref: "#/components/schemas/McpServer" + description: servers is the accumulated requested set, not live gateway truth. + reused: + type: boolean + description: reused is true when the response reused an existing gateway. + uid: + type: + - string + - "null" + description: (IMMUTABLE) uid is the existing immutable backing identity, omitted when none is available. + readOnly: true + name: + type: string + description: (IDENTIFIER) name is the complete singleton resource name. + title: McpGateway + description: McpGateway is gateway state for one sandbox. + McpGatewayState: + type: string + title: McpGatewayState + enum: + - unspecified + - provisioning + - ready + - failed + description: |- + McpGatewayState reports gateway readiness. + + Values: + unspecified: unspecified is never a concrete known state. + provisioning: provisioning means the gateway is starting. + ready: ready means the gateway can accept server operations. + failed: failed means gateway setup failed. + McpServer: + type: object + properties: + name: + type: string + description: name is the external configured server key, not an API resource name. + type: + description: type is the configured server type. + $ref: "#/components/schemas/McpServerType" + title: McpServer + description: McpServer projects one external server key configured on a gateway. + McpServerType: + type: string + title: McpServerType + enum: + - unspecified + - remote + - container + - stdio + description: |- + McpServerType identifies a gateway server integration style. + + Values: + unspecified: unspecified is not a concrete server type. + remote: remote is an HTTP remote MCP server. + container: container is a container-backed MCP server. + stdio: stdio is a stdio-backed MCP server. + NetworkPolicy: + type: object + properties: + mode: + type: string + title: NetworkPolicyMode + enum: + - denyAll + description: mode is required and cannot be UNSPECIFIED. + not: + enum: + - unspecified + allowNetworks: + type: array + items: + type: string + description: allow_networks are exact network names or CIDRs allowed by this document. + denyNetworks: + type: array + items: + type: string + description: deny_networks are exact network names or CIDRs denied by this document. + title: NetworkPolicy + required: + - mode + additionalProperties: false + description: NetworkPolicy is an inline policy request materialized in the governance plane. + NetworkPolicyMode: + type: string + title: NetworkPolicyMode + enum: + - unspecified + - allowAll + - denyAll + description: |- + NetworkPolicyMode is the top-level egress disposition. + + Values: + unspecified: unspecified is invalid in policy documents and in conforming responses. + allowAll: allowAll allows egress unless denied by a narrower rule. + denyAll: denyAll denies egress unless allowed by a narrower rule. + OAuthConfig: + type: object + properties: + tokenEndpoint: + type: string + description: token_endpoint is the OAuth token endpoint. + clientId: + type: string + description: client_id is the OAuth client id. + title: OAuthConfig + description: OAuthConfig is non-secret OAuth metadata returned on reads. + OAuthRefreshMaterial: + type: object + properties: + refreshToken: + type: string + minLength: 1 + description: refresh_token is the OAuth refresh token. + writeOnly: true + initialAccessToken: + type: string + description: initial_access_token is an optional bootstrap access token. + writeOnly: true + expiresAt: + description: expires_at is the access token expiry when supplied. + $ref: "#/components/schemas/Timestamp" + scopes: + type: array + items: + type: string + description: scopes are requested OAuth scopes. + providerAccountId: + type: string + description: provider_account_id identifies the provider account. + idToken: + type: string + description: id_token is an optional OpenID Connect id token. + writeOnly: true + clientId: + type: string + description: client_id is the OAuth client id. + title: OAuthRefreshMaterial + required: + - refreshToken + additionalProperties: false + description: |- + OAuthRefreshMaterial is OAuth secret material. Unlike OAuthRefreshMaterialV2 it pairs + no bootstrap token with an expiry and stores client_id as the caller supplied it. + PolicyDecision: + type: string + title: PolicyDecision + enum: + - unspecified + - allowed + - blocked + description: |- + PolicyDecision is the outcome of an evaluated access attempt. + + Values: + unspecified: unspecified means the decision is unknown or not reported. + allowed: allowed means the attempt was allowed. + blocked: blocked means the attempt was blocked. + PolicyDomain: + type: string + title: PolicyDomain + enum: + - unspecified + - network + description: |- + PolicyDomain identifies a policy subsystem. + + Values: + unspecified: unspecified is not a concrete policy domain. + network: network is network egress policy. + PolicyLayer: + type: string + title: PolicyLayer + enum: + - unspecified + - org + - owner + - kit + - attached + - gateway + - default + description: |- + PolicyLayer identifies where an effective rule came from. ORG and OWNER are + the two governance scopes; exactly one governs a caller, so a conforming effective view never mixes them. + + Values: + unspecified: unspecified means the layer is unknown or not reported. + org: org is org-level governance policy. + owner: owner is owner-level governance policy. + kit: kit is the sandbox-scoped policy a kit contributes. + attached: attached is a governance policy bound to the sandbox: an inline document or a referenced policy id, materialized and bound by the governance plane at create. + gateway: gateway is an enforced runtime-owned gateway policy contribution, not a backend service-route exemption. + default: default is an explicit installed default-policy contribution, not a substitute for unknown provenance or an owner-level policy. + PolicyLogEntry: + type: object + properties: + resource: + type: string + description: resource is the requested destination or resource. + decision: + description: decision is the observed allow or block outcome. + $ref: "#/components/schemas/PolicyDecision" + proxyType: + type: string + description: proxy_type is the proxy or enforcement path that observed the decision. + rule: + type: string + description: rule is the matching rule when available. + reason: + type: string + description: reason is backend-readable context. + count: + type: + - integer + - string + format: int64 + description: count is the number of coalesced observations. + firstSeen: + description: first_seen is the first observation timestamp. + $ref: "#/components/schemas/Timestamp" + lastSeen: + description: last_seen is the most recent observation timestamp. + $ref: "#/components/schemas/Timestamp" + sandbox: + type: string + description: sandbox is the resource name recorded for this reference. + sandboxUid: + type: + - string + - "null" + description: sandbox_uid retains the observed backing incarnation when the backend provides one. + readOnly: true + domain: + description: domain is the policy domain that emitted the entry. + $ref: "#/components/schemas/PolicyDomain" + title: PolicyLogEntry + description: PolicyLogEntry is an aggregated observed policy decision. + Port: + type: object + properties: + number: + type: integer + maximum: 65535 + minimum: 1 + format: int32 + description: (IMMUTABLE) number is immutable and unique within the sandbox; duplicates refuse ALREADY_EXISTS. + protocol: + description: (IMMUTABLE) protocol defaults to TCP when unspecified. + $ref: "#/components/schemas/Protocol" + url: + type: string + description: url is the required externally reachable address, including host-local addresses. + readOnly: true + etag: + type: string + description: etag identifies the observed version of this port. + readOnly: true + uid: + type: + - string + - "null" + description: (IMMUTABLE) uid is the durable publication incarnation when the backend provides one. + readOnly: true + name: + type: string + description: (IDENTIFIER) name identifies the published port number under its sandbox resource name. + title: Port + required: + - number + description: |- + Port is one sandbox port made reachable from outside the sandbox. The same + facts appear inline on SandboxCore.ports for a caller reading the sandbox. + RawImageStartup: + type: object + properties: + start: + description: start replaces the complete launch argv; omission uses the image command. + $ref: "#/components/schemas/StartupArgv" + title: RawImageStartup + additionalProperties: false + description: RawImageStartup resolves identity and working directory from the pinned image. + Sandbox: + type: object + properties: + core: + description: core is the backend-neutral sandbox view. + $ref: "#/components/schemas/SandboxCore" + effectiveFeatures: + description: effective_features is emitted only by the composition contract projection. + $ref: "#/components/schemas/EffectiveFeatures" + failure: + description: failure is present only while status is FAILED and clears on recovery. + readOnly: true + $ref: "#/components/schemas/Error" + name: + type: string + pattern: ^sandboxes/[^/]+$ + description: (IDENTIFIER) name ends in the immutable backing ID and never changes with display_name. + uid: + type: + - string + minLength: 1 + description: (IMMUTABLE) uid is the existing backing ID, equal to the resource name's terminal segment. + readOnly: true + displayName: + type: string + description: display_name is the scoped label; UpdateSandbox changes it without changing identity. + title: Sandbox + required: + - core + - name + - uid + description: |- + Sandbox carries shared core state and the effective state of its selected features. + uid must equal the immutable resource ID in name + Secret: + type: object + properties: + uid: + type: + - string + minLength: 1 + description: (IMMUTABLE) uid is the existing backing ID, equal to the resource name's terminal segment. + readOnly: true + name: + type: string + pattern: ^secrets/[^/]+$ + description: (IDENTIFIER) name ends in the immutable backing ID and never changes with display_name. + serviceType: + type: string + description: service_type identifies the consuming service. + type: + description: type identifies the stored material shape. + $ref: "#/components/schemas/SecretType" + oauthConfig: + description: oauth_config is returned only for OAuth-shaped secrets. + $ref: "#/components/schemas/OAuthConfig" + scopes: + type: array + items: + type: string + description: scopes are non-secret OAuth scopes. + createdAt: + description: created_at is the creation timestamp. + $ref: "#/components/schemas/Timestamp" + updatedAt: + description: updated_at is the last update timestamp. + $ref: "#/components/schemas/Timestamp" + injection: + description: injection is returned only for custom-shaped secrets. + $ref: "#/components/schemas/CustomInjection" + placeholder: + type: string + description: |- + placeholder is server-generated per custom secret and stable across value + rotation; it is not secret material. A client presents it where the + credential would go; for the configured hosts the backend replaces the + configured header's value at egress regardless of what was sent. + etag: + type: string + minLength: 1 + pattern: ^"[^"\x00-\x20\x7f]*"$ + description: |- + etag identifies the observed version of this resource. It is opaque and strong, + changes on every visible change, and is what a mutation sends as its precondition. + readOnly: true + displayName: + type: string + description: display_name is a scoped label and never selects the resource. + title: Secret + required: + - uid + - name + - createdAt + - updatedAt + - etag + description: |- + Secret is returned metadata and never includes secret material. + uid must equal the immutable resource ID in name + SecretType: + type: string + title: SecretType + enum: + - unspecified + - token + - oauthRefresh + - custom + - registryV2 + - hostResolved + description: "The stored credential shape: token, OAuth refresh material, custom injection, registry credentials, or a credential resolved from an approved host-owned source. Secret material is never returned." + Snapshot: + type: object + properties: + uid: + type: + - string + minLength: 1 + description: (IMMUTABLE) uid is the existing backing ID, equal to the resource name's terminal segment. + readOnly: true + name: + type: string + pattern: ^snapshots/[^/]+$ + description: (IDENTIFIER) name ends in the immutable backing ID and never changes with display_name. + description: + type: string + description: description is caller-supplied text. + sandbox: + type: string + description: sandbox is the resource name recorded for this reference. + sandboxUid: + type: + - string + - "null" + description: sandbox_uid retains the observed backing incarnation when the backend provides one. + readOnly: true + status: + not: + enum: + - unspecified + description: status is the snapshot readiness state. + readOnly: true + $ref: "#/components/schemas/SnapshotStatus" + totalSizeBytes: + type: + - integer + - string + format: int64 + description: total_size_bytes is the approximate stored snapshot size. + resources: + description: resources describes the captured sandbox resources. + $ref: "#/components/schemas/Resources" + platform: + description: platform is the captured sandbox platform. + $ref: "#/components/schemas/Platform" + createdAt: + description: created_at is the creation timestamp. + $ref: "#/components/schemas/Timestamp" + failure: + description: failure is set when status is FAILED. + $ref: "#/components/schemas/Error" + captureMode: + description: capture_mode is the mode the snapshot was captured with. + $ref: "#/components/schemas/CaptureMode" + etag: + type: string + minLength: 1 + pattern: ^"[^"\x00-\x20\x7f]*"$ + description: |- + etag identifies the observed version of this resource. It is opaque and strong, + changes on every visible change, and is what a mutation sends as its precondition. + readOnly: true + displayName: + type: string + description: display_name is a scoped label and never selects the resource. + title: Snapshot + required: + - uid + - name + - status + - createdAt + - etag + description: |- + Snapshot is the detailed snapshot view. + uid must equal the immutable resource ID in name + SnapshotStatus: + type: string + title: SnapshotStatus + enum: + - unspecified + - creating + - ready + - failed + - deleted + description: |- + SnapshotStatus is snapshot readiness state. + + Values: + unspecified: unspecified is never a concrete known state. + creating: creating means capture has not completed. + ready: ready means the snapshot can be restored. + failed: failed means capture reached a terminal failure. + deleted: deleted means the backend exposes a retained delete tombstone. + SnapshotSummary: + type: object + properties: + uid: + type: + - string + minLength: 1 + description: (IMMUTABLE) uid is the existing backing ID, equal to the resource name's terminal segment. + readOnly: true + name: + type: string + pattern: ^snapshots/[^/]+$ + description: name ends in the immutable backing ID and never changes with display_name. + sandbox: + type: string + description: sandbox is the resource name recorded for this reference. + sandboxUid: + type: + - string + - "null" + description: sandbox_uid retains the observed backing incarnation when the backend provides one. + readOnly: true + status: + not: + enum: + - unspecified + description: status is the snapshot readiness state. + $ref: "#/components/schemas/SnapshotStatus" + totalSizeBytes: + type: + - integer + - string + format: int64 + description: total_size_bytes is the approximate stored snapshot size. + createdAt: + description: created_at is the creation timestamp. + $ref: "#/components/schemas/Timestamp" + captureMode: + description: capture_mode is the mode the snapshot was captured with. + $ref: "#/components/schemas/CaptureMode" + displayName: + type: string + description: display_name is a scoped label and never selects the resource. + title: SnapshotSummary + required: + - uid + - name + - status + - createdAt + description: |- + SnapshotSummary is the list view of a snapshot. + uid must equal the immutable resource ID in name + StartupArgv: + type: object + properties: + argv: + type: array + items: + type: string + minItems: 1 + description: |- + argv is executed without shell expansion. It carries at least one element, + and that first element is the executable and must not be empty. + title: StartupArgv + required: + - argv + additionalProperties: false + description: |- + StartupArgv preserves literal arguments, including empty positional arguments. + startup argv requires a nonempty executable + StartupExecution: + type: object + oneOf: + - type: object + properties: + rawImage: + description: raw_image executes image commands using OCI identity and directory defaults. + $ref: "#/components/schemas/RawImageStartup" + title: raw_image + required: + - rawImage + title: StartupExecution + unevaluatedProperties: false + description: |- + StartupExecution retains the accepted execution promise across fresh boots. + It is ordinary create input. + StopMcpGatewayResponse: + type: object + title: StopMcpGatewayResponse + description: StopMcpGatewayResponse may retain the fenced desired configuration without claiming a live gateway. + TokenSecretMaterial: + type: object + properties: + value: + type: string + minLength: 1 + description: value is the secret token. + writeOnly: true + title: TokenSecretMaterial + required: + - value + additionalProperties: false + description: TokenSecretMaterial is opaque token material. + Volume: + type: object + properties: + uid: + type: + - string + minLength: 1 + description: (IMMUTABLE) uid is the existing backing ID, equal to the resource name's terminal segment. + readOnly: true + name: + type: string + pattern: ^volumes/[^/]+$ + description: (IDENTIFIER) name ends in the immutable backing ID and never changes with display_name. + createdAt: + description: created_at is the creation timestamp. + $ref: "#/components/schemas/Timestamp" + etag: + type: string + minLength: 1 + pattern: ^"[^"\x00-\x20\x7f]*"$ + description: |- + etag identifies the observed version of this resource. It is opaque and strong, + changes on every visible change, and is what a mutation sends as its precondition. + readOnly: true + displayName: + type: string + description: display_name is a scoped label and never selects the resource. + title: Volume + required: + - uid + - name + - createdAt + - etag + description: |- + Volume is persistent storage metadata within its owner and contract scope. + uid must equal the immutable resource ID in name + Duration: + type: string + pattern: ^-?\d+(\.\d{1,9})?s$ + description: Seconds with a trailing s, for example 30s or 1.5s. + examples: + - 30s + - 1.5s + ValidationFieldType: + type: string + title: Type + enum: + - TYPE_DOUBLE + - TYPE_FLOAT + - TYPE_INT64 + - TYPE_UINT64 + - TYPE_INT32 + - TYPE_FIXED64 + - TYPE_FIXED32 + - TYPE_BOOL + - TYPE_STRING + - TYPE_GROUP + - TYPE_MESSAGE + - TYPE_BYTES + - TYPE_UINT32 + - TYPE_ENUM + - TYPE_SFIXED32 + - TYPE_SFIXED64 + - TYPE_SINT32 + - TYPE_SINT64 + Timestamp: + type: string + examples: + - 2023-01-15T01:30:15.01Z + - 2024-12-25T12:00:00Z + format: date-time + description: An RFC 3339 timestamp in UTC, for example 2026-01-02T03:04:05Z. + BadRequest: + type: object + properties: + fieldViolations: + type: array + items: + $ref: "#/components/schemas/BadRequestFieldViolation" + description: Describes all violations in a client request. + title: BadRequest + description: |- + Describes violations in a client request. This error type focuses on the + syntactic aspects of the request. + BadRequestFieldViolation: + type: object + properties: + field: + type: string + description: |- + A path that leads to a field in the request body. The value will be a + sequence of dot-separated identifiers that identify a protocol buffer + field. + + Consider the following: + + message CreateContactRequest { + message EmailAddress { + enum Type { + TYPE_UNSPECIFIED = 0; + HOME = 1; + WORK = 2; + } + + optional string email = 1; + repeated EmailType type = 2; + } + + string full_name = 1; + repeated EmailAddress email_addresses = 2; + } + + In this example, in proto `field` could take one of the following values: + + * `full_name` for a violation in the `full_name` value + * `email_addresses[0].email` for a violation in the `email` field of the + first `email_addresses` message + * `email_addresses[2].type[1]` for a violation in the second `type` + value in the third `email_addresses` message. + + In JSON, the same values are represented as: + + * `fullName` for a violation in the `fullName` value + * `emailAddresses[0].email` for a violation in the `email` field of the + first `emailAddresses` message + * `emailAddresses[2].type[1]` for a violation in the second `type` + value in the third `emailAddresses` message. + description: + type: string + description: A description of why the request element is bad. + reason: + type: string + description: |- + The reason of the field-level error. This is a constant value that + identifies the proximate cause of the field-level error. It should + uniquely identify the type of the FieldViolation within the scope of the + ErrorInfo.domain. This should be at most 63 + characters and match a regular expression of `[A-Z][A-Z0-9_]+[A-Z0-9]`, + which represents UPPER_SNAKE_CASE. + localizedMessage: + description: |- + Provides a localized error message for field-level errors that is safe to + return to the API consumer. + $ref: "#/components/schemas/LocalizedMessage" + title: FieldViolation + description: A message type used to describe a single bad request field. + BadRequestErrorDetail: + type: object + properties: + fieldViolations: + type: array + items: + $ref: "#/components/schemas/BadRequestFieldViolation" + description: Describes all violations in a client request. + "@type": + type: string + const: type.googleapis.com/google.rpc.BadRequest + title: BadRequestErrorDetail + description: |- + Describes violations in a client request. This error type focuses on the + syntactic aspects of the request. + required: + - "@type" + DebugInfo: + type: object + properties: + stackEntries: + type: array + items: + type: string + description: The stack trace entries indicating where the error occurred. + detail: + type: string + description: Additional debugging information provided by the server. + title: DebugInfo + description: Describes additional debugging info. + DebugInfoErrorDetail: + type: object + properties: + stackEntries: + type: array + items: + type: string + description: The stack trace entries indicating where the error occurred. + detail: + type: string + description: Additional debugging information provided by the server. + "@type": + type: string + const: type.googleapis.com/google.rpc.DebugInfo + title: DebugInfoErrorDetail + description: Describes additional debugging info. + required: + - "@type" + ErrorInfo: + type: object + properties: + reason: + type: string + description: |- + The reason of the error. This is a constant value that identifies the + proximate cause of the error. Error reasons are unique within a particular + domain of errors. This should be at most 63 characters and match a + regular expression of `[A-Z][A-Z0-9_]+[A-Z0-9]`, which represents + UPPER_SNAKE_CASE. + domain: + type: string + description: |- + The logical grouping to which the "reason" belongs. The error domain + is typically the registered service name of the tool or product that + generates the error. Example: "pubsub.googleapis.com". If the error is + generated by some common infrastructure, the error domain must be a + globally unique value that identifies the infrastructure. For Google API + infrastructure, the error domain is "googleapis.com". + metadata: + type: object + additionalProperties: + type: string + title: value + description: |- + Additional structured details about this error. + + Keys must match a regular expression of `[a-z][a-zA-Z0-9-_]+` but should + ideally be lowerCamelCase. Also, they must be limited to 64 characters in + length. When identifying the current value of an exceeded limit, the units + should be contained in the key, not the value. For example, rather than + `{"instanceLimit": "100/request"}`, should be returned as, + `{"instanceLimitPerRequest": "100"}`, if the client exceeds the number of + instances that can be created in a single (batch) request. + title: ErrorInfo + description: |- + Describes the cause of the error with structured details. + + Example of an error when contacting the "pubsub.googleapis.com" API when it + is not enabled: + + { "reason": "API_DISABLED" + "domain": "googleapis.com" + "metadata": { + "resource": "projects/123", + "service": "pubsub.googleapis.com" + } + } + + This response indicates that the pubsub.googleapis.com API is not enabled. + + Example of an error that is returned when attempting to create a Spanner + instance in a region that is out of stock: + + { "reason": "STOCKOUT" + "domain": "spanner.googleapis.com", + "metadata": { + "availableRegions": "us-central1,us-east2" + } + } + ErrorInfoErrorDetail: + type: object + properties: + reason: + type: string + description: |- + The reason of the error. This is a constant value that identifies the + proximate cause of the error. Error reasons are unique within a particular + domain of errors. This should be at most 63 characters and match a + regular expression of `[A-Z][A-Z0-9_]+[A-Z0-9]`, which represents + UPPER_SNAKE_CASE. + domain: + type: string + description: |- + The logical grouping to which the "reason" belongs. The error domain + is typically the registered service name of the tool or product that + generates the error. Example: "pubsub.googleapis.com". If the error is + generated by some common infrastructure, the error domain must be a + globally unique value that identifies the infrastructure. For Google API + infrastructure, the error domain is "googleapis.com". + metadata: + type: object + additionalProperties: + type: string + title: value + description: |- + Additional structured details about this error. + + Keys must match a regular expression of `[a-z][a-zA-Z0-9-_]+` but should + ideally be lowerCamelCase. Also, they must be limited to 64 characters in + length. When identifying the current value of an exceeded limit, the units + should be contained in the key, not the value. For example, rather than + `{"instanceLimit": "100/request"}`, should be returned as, + `{"instanceLimitPerRequest": "100"}`, if the client exceeds the number of + instances that can be created in a single (batch) request. + "@type": + type: string + const: type.googleapis.com/google.rpc.ErrorInfo + title: ErrorInfoErrorDetail + description: |- + Describes the cause of the error with structured details. + + Example of an error when contacting the "pubsub.googleapis.com" API when it + is not enabled: + + { "reason": "API_DISABLED" + "domain": "googleapis.com" + "metadata": { + "resource": "projects/123", + "service": "pubsub.googleapis.com" + } + } + + This response indicates that the pubsub.googleapis.com API is not enabled. + + Example of an error that is returned when attempting to create a Spanner + instance in a region that is out of stock: + + { "reason": "STOCKOUT" + "domain": "spanner.googleapis.com", + "metadata": { + "availableRegions": "us-central1,us-east2" + } + } + required: + - "@type" + Help: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/HelpLink" + description: URL(s) pointing to additional information on handling the current error. + title: Help + description: |- + Provides links to documentation or for performing an out of band action. + + For example, if a quota check failed with an error indicating the calling + project hasn't enabled the accessed service, this can contain a URL pointing + directly to the right place in the developer console to flip the bit. + HelpLink: + type: object + properties: + description: + type: string + description: Describes what the link offers. + url: + type: string + description: The URL of the link. + title: Link + description: Describes a URL link. + HelpErrorDetail: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/HelpLink" + description: URL(s) pointing to additional information on handling the current error. + "@type": + type: string + const: type.googleapis.com/google.rpc.Help + title: HelpErrorDetail + description: |- + Provides links to documentation or for performing an out of band action. + + For example, if a quota check failed with an error indicating the calling + project hasn't enabled the accessed service, this can contain a URL pointing + directly to the right place in the developer console to flip the bit. + required: + - "@type" + LocalizedMessage: + type: object + properties: + locale: + type: string + description: |- + The locale used following the specification defined at + https://www.rfc-editor.org/rfc/bcp/bcp47.txt. + Examples are: "en-US", "fr-CH", "es-MX" + message: + type: string + description: The localized error message in the above locale. + title: LocalizedMessage + description: |- + Provides a localized error message that is safe to return to the user + which can be attached to an operation error. + LocalizedMessageErrorDetail: + type: object + properties: + locale: + type: string + description: |- + The locale used following the specification defined at + https://www.rfc-editor.org/rfc/bcp/bcp47.txt. + Examples are: "en-US", "fr-CH", "es-MX" + message: + type: string + description: The localized error message in the above locale. + "@type": + type: string + const: type.googleapis.com/google.rpc.LocalizedMessage + title: LocalizedMessageErrorDetail + description: |- + Provides a localized error message that is safe to return to the user + which can be attached to an operation error. + required: + - "@type" + PreconditionFailure: + type: object + properties: + violations: + type: array + items: + $ref: "#/components/schemas/PreconditionFailureViolation" + description: Describes all precondition violations. + title: PreconditionFailure + description: |- + Describes what preconditions have failed. + + For example, if an operation failed because it required the Terms of Service to be + acknowledged, it could list the terms of service violation in the + PreconditionFailure message. + PreconditionFailureViolation: + type: object + properties: + type: + type: string + description: |- + The type of PreconditionFailure. We recommend using a service-specific + enum type to define the supported precondition violation subjects. For + example, "TOS" for "Terms of Service violation". + subject: + type: string + description: |- + The subject, relative to the type, that failed. + For example, "google.com/cloud" relative to the "TOS" type would indicate + which terms of service is being referenced. + description: + type: string + description: |- + A description of how the precondition failed. Developers can use this + description to understand how to fix the failure. + + For example: "Terms of service not accepted". + title: Violation + description: A message type used to describe a single precondition failure. + PreconditionFailureErrorDetail: + type: object + properties: + violations: + type: array + items: + $ref: "#/components/schemas/PreconditionFailureViolation" + description: Describes all precondition violations. + "@type": + type: string + const: type.googleapis.com/google.rpc.PreconditionFailure + title: PreconditionFailureErrorDetail + description: |- + Describes what preconditions have failed. + + For example, if an operation failed because it required the Terms of Service to be + acknowledged, it could list the terms of service violation in the + PreconditionFailure message. + required: + - "@type" + QuotaFailure: + type: object + properties: + violations: + type: array + items: + $ref: "#/components/schemas/QuotaFailureViolation" + description: Describes all quota violations. + title: QuotaFailure + description: |- + Describes how a quota check failed. + + For example if a daily limit was exceeded for the calling project, + a service could respond with a QuotaFailure detail containing the project + id and the description of the quota limit that was exceeded. If the + calling project hasn't enabled the service in the developer console, then + a service could respond with the project id and set `service_disabled` + to true. + + Also see RetryInfo and Help types for other details about handling a + quota failure. + QuotaFailureViolation: + type: object + properties: + subject: + type: string + description: |- + The subject on which the quota check failed. + For example, "clientip:" or "project:". + description: + type: string + description: |- + A description of how the quota check failed. Clients can use this + description to find more about the quota configuration in the service's + public documentation, or find the relevant quota limit to adjust through + developer console. + + For example: "Service disabled" or "Daily Limit for read operations + exceeded". + apiService: + type: string + description: |- + The API Service from which the `QuotaFailure.Violation` orginates. In + some cases, Quota issues originate from an API Service other than the one + that was called. In other words, a dependency of the called API Service + could be the cause of the `QuotaFailure`, and this field would have the + dependency API service name. + + For example, if the called API is Kubernetes Engine API + (container.googleapis.com), and a quota violation occurs in the + Kubernetes Engine API itself, this field would be + "container.googleapis.com". On the other hand, if the quota violation + occurs when the Kubernetes Engine API creates VMs in the Compute Engine + API (compute.googleapis.com), this field would be + "compute.googleapis.com". + quotaMetric: + type: string + description: |- + The metric of the violated quota. A quota metric is a named counter to + measure usage, such as API requests or CPUs. When an activity occurs in a + service, such as Virtual Machine allocation, one or more quota metrics + may be affected. + + For example, "compute.googleapis.com/cpus_per_vm_family", + "storage.googleapis.com/internet_egress_bandwidth". + quotaId: + type: string + description: |- + The id of the violated quota. Also know as "limit name", this is the + unique identifier of a quota in the context of an API service. + + For example, "CPUS-PER-VM-FAMILY-per-project-region". + quotaDimensions: + type: object + additionalProperties: + type: string + title: value + description: |- + The dimensions of the violated quota. Every non-global quota is enforced + on a set of dimensions. While quota metric defines what to count, the + dimensions specify for what aspects the counter should be increased. + + For example, the quota "CPUs per region per VM family" enforces a limit + on the metric "compute.googleapis.com/cpus_per_vm_family" on dimensions + "region" and "vm_family". And if the violation occurred in region + "us-central1" and for VM family "n1", the quota_dimensions would be, + + { + "region": "us-central1", + "vm_family": "n1", + } + + When a quota is enforced globally, the quota_dimensions would always be + empty. + quotaValue: + type: + - integer + - string + format: int64 + description: |- + The enforced quota value at the time of the `QuotaFailure`. + + For example, if the enforced quota value at the time of the + `QuotaFailure` on the number of CPUs is "10", then the value of this + field would reflect this quantity. + futureQuotaValue: + type: + - integer + - string + - "null" + format: int64 + description: |- + The new quota value being rolled out at the time of the violation. At the + completion of the rollout, this value will be enforced in place of + quota_value. If no rollout is in progress at the time of the violation, + this field is not set. + + For example, if at the time of the violation a rollout is in progress + changing the number of CPUs quota from 10 to 20, 20 would be the value of + this field. + title: Violation + description: |- + A message type used to describe a single quota violation. For example, a + daily quota or a custom quota that was exceeded. + QuotaFailureErrorDetail: + type: object + properties: + violations: + type: array + items: + $ref: "#/components/schemas/QuotaFailureViolation" + description: Describes all quota violations. + "@type": + type: string + const: type.googleapis.com/google.rpc.QuotaFailure + title: QuotaFailureErrorDetail + description: |- + Describes how a quota check failed. + + For example if a daily limit was exceeded for the calling project, + a service could respond with a QuotaFailure detail containing the project + id and the description of the quota limit that was exceeded. If the + calling project hasn't enabled the service in the developer console, then + a service could respond with the project id and set `service_disabled` + to true. + + Also see RetryInfo and Help types for other details about handling a + quota failure. + required: + - "@type" + RequestInfo: + type: object + properties: + requestId: + type: string + description: |- + An opaque string that should only be interpreted by the service generating + it. For example, it can be used to identify requests in the service's logs. + servingData: + type: string + description: |- + Any data that was used to serve this request. For example, an encrypted + stack trace that can be sent back to the service provider for debugging. + title: RequestInfo + description: |- + Contains metadata about the request that clients can attach when filing a bug + or providing other forms of feedback. + RequestInfoErrorDetail: + type: object + properties: + requestId: + type: string + description: |- + An opaque string that should only be interpreted by the service generating + it. For example, it can be used to identify requests in the service's logs. + servingData: + type: string + description: |- + Any data that was used to serve this request. For example, an encrypted + stack trace that can be sent back to the service provider for debugging. + "@type": + type: string + const: type.googleapis.com/google.rpc.RequestInfo + title: RequestInfoErrorDetail + description: |- + Contains metadata about the request that clients can attach when filing a bug + or providing other forms of feedback. + required: + - "@type" + ResourceInfo: + type: object + properties: + resourceType: + type: string + description: |- + A name for the type of resource being accessed, e.g. "sql table", + "cloud storage bucket", "file", "Google calendar"; or the type URL + of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic". + resourceName: + type: string + description: |- + The name of the resource being accessed. For example, a shared calendar + name: "example.com_4fghdhgsrgh@group.calendar.google.com", if the current + error is + [CodePERMISSION_DENIED][CodePERMISSION_DENIED]. + owner: + type: string + description: |- + The owner of the resource (optional). + For example, "user:" or "project:". + description: + type: string + description: |- + Describes what error is encountered when accessing this resource. + For example, updating a cloud project may require the `writer` permission + on the developer console project. + title: ResourceInfo + description: Describes the resource that is being accessed. + ResourceInfoErrorDetail: + type: object + properties: + resourceType: + type: string + description: |- + A name for the type of resource being accessed, e.g. "sql table", + "cloud storage bucket", "file", "Google calendar"; or the type URL + of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic". + resourceName: + type: string + description: |- + The name of the resource being accessed. For example, a shared calendar + name: "example.com_4fghdhgsrgh@group.calendar.google.com", if the current + error is + [CodePERMISSION_DENIED][CodePERMISSION_DENIED]. + owner: + type: string + description: |- + The owner of the resource (optional). + For example, "user:" or "project:". + description: + type: string + description: |- + Describes what error is encountered when accessing this resource. + For example, updating a cloud project may require the `writer` permission + on the developer console project. + "@type": + type: string + const: type.googleapis.com/google.rpc.ResourceInfo + title: ResourceInfoErrorDetail + description: Describes the resource that is being accessed. + required: + - "@type" + RetryInfo: + type: object + properties: + retryDelay: + description: Clients should wait at least this long between retrying the same request. + $ref: "#/components/schemas/Duration" + title: RetryInfo + description: |- + Describes when the clients can retry a failed request. Clients could ignore + the recommendation here or retry when this information is missing from error + responses. + + It's always recommended that clients should use exponential backoff when + retrying. + + Clients should wait until `retry_delay` amount of time has passed since + receiving the error response before retrying. If retrying requests also + fail, clients should use an exponential backoff scheme to gradually increase + the delay between retries based on `retry_delay`, until either a maximum + number of retries have been reached or a maximum retry delay cap has been + reached. + RetryInfoErrorDetail: + type: object + properties: + retryDelay: + description: Clients should wait at least this long between retrying the same request. + $ref: "#/components/schemas/Duration" + "@type": + type: string + const: type.googleapis.com/google.rpc.RetryInfo + title: RetryInfoErrorDetail + description: |- + Describes when the clients can retry a failed request. Clients could ignore + the recommendation here or retry when this information is missing from error + responses. + + It's always recommended that clients should use exponential backoff when + retrying. + + Clients should wait until `retry_delay` amount of time has passed since + receiving the error response before retrying. If retrying requests also + fail, clients should use an exponential backoff scheme to gradually increase + the delay between retries based on `retry_delay`, until either a maximum + number of retries have been reached or a maximum retry delay cap has been + reached. + required: + - "@type" + UpdateSecretBody: + type: object + allOf: + - properties: + serviceType: + type: string + description: service_type identifies the consuming service. + - oneOf: + - type: object + properties: + custom: + description: |- + custom stores an opaque value with caller-authored injection metadata. + UpdateSecretRequest.service_type is server-assigned for custom + material; leave it empty. + $ref: "#/components/schemas/CustomSecretMaterial" + title: custom + required: + - custom + - type: object + properties: + oauth: + description: oauth stores OAuth refresh material. + $ref: "#/components/schemas/OAuthRefreshMaterial" + title: oauth + required: + - oauth + - type: object + properties: + token: + description: token stores opaque token material. + $ref: "#/components/schemas/TokenSecretMaterial" + title: token + required: + - token + title: UpdateSecretRequest + unevaluatedProperties: false + description: |- + UpdateSecretRequest replaces secret material. + service_type must be empty for custom material (server-assigned) + securitySchemes: + bearer: + type: http + scheme: bearer + description: A bearer credential is required for every HTTP request. Management operations use an API credential; sandbox endpoint operations use an issued credential scoped to the sandbox, audience and permitted actions. + sandboxBearer: + type: http + scheme: bearer + description: A short-lived bearer issued by createEndpointCredential for this sandbox, audience and explicit permissions. Management credentials are not accepted. + sandboxWebSocketBearer: + type: apiKey + in: header + name: Sec-WebSocket-Protocol + description: Sandbox-scoped credential encoded in the non-echoed subprotocol offer declared by x-websocket. Offer the API subprotocol alongside it; use this carrier only when endpoint discovery advertises webSocketSubprotocol. Never combine it with Authorization. +security: + - bearer: [] +tags: + - name: Files + description: Read, write, copy, and manage files inside a sandbox. + x-displayName: Files + kind: nav + summary: Files + - name: Processes + description: Run commands and interact with processes inside a sandbox. + x-displayName: Processes + kind: nav + summary: Processes + - name: Credentials + description: Exchange credentials for the authenticated owner. + x-displayName: Credentials + kind: nav + summary: Credentials + - name: Images + description: Manage images used to create sandboxes. + x-displayName: Images + kind: nav + summary: Images + - name: MCP gateways + description: Configure MCP gateways and authorize upstream servers. + x-displayName: MCP gateways + kind: nav + summary: MCP gateways + - name: Network policies + description: Read enforced outbound network policies and their decision logs. + x-displayName: Network policies + kind: nav + summary: Network policies + - name: Sandboxes + description: Create sandboxes and manage their lifecycle, ports, and SSH access. + x-displayName: Sandboxes + kind: nav + summary: Sandboxes + - name: Secrets + description: Store credentials and read their metadata without returning secret material. + x-displayName: Secrets + kind: nav + summary: Secrets + - name: Snapshots + description: Capture sandbox state and restore it into a new sandbox. + x-displayName: Snapshots + kind: nav + summary: Snapshots + - name: Volumes + description: Manage persistent storage and attach it to sandboxes. + x-displayName: Volumes + kind: nav + summary: Volumes +jsonSchemaDialect: https://spec.openapis.org/oas/3.1/dialect/base diff --git a/hack/api-docs/README.md b/hack/api-docs/README.md index d346e397e8..4cbf7a4303 100644 --- a/hack/api-docs/README.md +++ b/hack/api-docs/README.md @@ -86,3 +86,7 @@ screens. Callbacks and webhook navigation are unsupported and fail validation. Request examples are POSIX shell templates; they do not make service calls. + +Examples are optional. Supplied examples must validate against their schemas. +When a request body has no example, the generated cURL command reads from +`request-body` and directs the reader to prepare that file from the schema. diff --git a/hack/api-docs/browser-checks.mjs b/hack/api-docs/browser-checks.mjs index 44a47eebfb..fe419e246e 100644 --- a/hack/api-docs/browser-checks.mjs +++ b/hack/api-docs/browser-checks.mjs @@ -8,8 +8,8 @@ export default async function verify(page, base = "http://localhost:1314") { await page.setViewportSize({ width: 1440, height: 1000 }); await page.goto(base + "/reference/api/"); assert( - (await page.locator(".api-card").count()) === 5, - "Five API catalog entries", + (await page.locator(".api-card").count()) === 6, + "Six API catalog entries", ); assert( (await page.locator("nav.navbar-font").count()) === 1, @@ -88,6 +88,41 @@ export default async function verify(page, base = "http://localhost:1314") { (await page.locator('[data-api-view="overview"]').count()) === 1, "Governance alias reaches generated overview", ); + await page.goto(base + "/ai/sandboxes-api/"); + assert( + (await page + .locator('nav.navbar-font a[href="/reference/api/sandboxes/latest/"]') + .count()) === 1, + "Sandboxes manual links to the API reference in the sidebar", + ); + await page.goto(base + "/reference/api/sandboxes/"); + await page.waitForURL("**/reference/api/sandboxes/latest/"); + assert( + (await page.locator("article").textContent()).includes("experimental"), + "Sandboxes alias reaches the experimental API overview", + ); + await page.goto( + base + "/reference/api/sandboxes/latest/operations/download/", + ); + assert( + (await page.locator("article").textContent()).includes("itemEncoding"), + "File download renders stream encoding", + ); + await page.goto( + base + "/reference/api/sandboxes/latest/operations/interact/", + ); + assert( + (await page.locator("article").textContent()).includes( + "WebSocket session", + ) && (await page.locator("[data-api-copy]").count()) === 0, + "Interactive processes show WebSocket requirements instead of a plain HTTP example", + ); + assert( + await page.evaluate( + () => document.documentElement.scrollWidth <= innerWidth + 1, + ), + "Sandboxes operation fits a narrow screen", + ); const context = await page .context() .browser() diff --git a/hack/api-docs/main.go b/hack/api-docs/main.go index 5e0b2831eb..475a2e07f5 100644 --- a/hack/api-docs/main.go +++ b/hack/api-docs/main.go @@ -100,14 +100,15 @@ type Diagnostic struct { Message string `json:"message"` } type Source struct { - ID string `json:"id"` - Product string `json:"product"` - Title string `json:"title"` - Source string `json:"source"` - Owner string `json:"owner"` - Manual string `json:"manual"` - Connection string `json:"connection"` - Guides []string `json:"guides"` + Experimental bool `json:"experimental,omitempty"` + ID string `json:"id"` + Product string `json:"product"` + Title string `json:"title"` + Source string `json:"source"` + Owner string `json:"owner"` + Manual string `json:"manual"` + Connection string `json:"connection"` + Guides []string `json:"guides"` } type Registry struct{ resources map[string]any } @@ -539,12 +540,7 @@ func (d *Document) validate(metaDir string) { d.issue("S8", str(param["pointer"])+"/description", "Parameter description requires editorial review") } } - for _, variant := range arr(op["variants"]) { - v := obj(variant) - if str(v["media"]) != "" && len(arr(v["examples"])) == 0 { - d.issue("S11", str(v["pointer"]), "Media variant needs a reviewed example or transfer fixture") - } - } + } } func (d *Document) schemaExamples(s any, p string) { @@ -646,7 +642,7 @@ func (d *Document) variants(op Object, p string) []any { for _, media := range keys(content) { v := obj(content[media]) a := Object{"direction": direction, "status": status, "description": container["description"], "headers": container["headers"], "media": media, "pointer": ptr + "/content/" + esc(media), "examples": d.mediaExamples(v), "required": container["required"]} - for _, k := range []string{"schema", "itemSchema", "encoding"} { + for _, k := range []string{"schema", "itemSchema", "encoding", "itemEncoding", "prefixEncoding"} { if s, ok := v[k]; ok { a[k] = s } diff --git a/hack/api-docs/main_test.go b/hack/api-docs/main_test.go index 2c12f9bb22..6fab054f05 100644 --- a/hack/api-docs/main_test.go +++ b/hack/api-docs/main_test.go @@ -47,6 +47,36 @@ func TestActualDialectAndReferences(t *testing.T) { t.Fatal("duplicate overridden parameter") } } + +func TestSequentialMediaEncodingPreserved(t *testing.T) { + d := fixture(t, "valid.yaml") + op := obj(obj(obj(d.Root["paths"])["/events"])["get"]) + response := obj(obj(op["responses"])["200"]) + encoding := Object{"contentType": "application/json", "headers": Object{ + "Content-Disposition": Object{"schema": Object{"type": "string"}}, + }} + response["content"] = Object{"multipart/mixed": Object{ + "itemSchema": true, "example": jsonNumber("0"), "itemEncoding": encoding, + }} + d.validate("") + if len(d.Diagnostics) != 0 { + t.Fatalf("sequential media fixture must be valid: %+v", d.Diagnostics) + } + variants := d.variants(op, "/paths/~1events/get") + if len(variants) != 1 || !reflect.DeepEqual(obj(variants[0])["itemEncoding"], encoding) { + t.Fatalf("stream content types and part headers were lost: %#v", variants) + } +} + +func TestWebSocketDoesNotProducePlainHTTPRequest(t *testing.T) { + op := Object{"method": "GET", "path": "/interact", "raw": Object{ + "x-websocket": Object{"subprotocol": "sandboxes.v1"}, + }} + command, notes := curlExample(Source{}, op) + if command != "" || len(notes) == 0 || !strings.Contains(notes[0], "WebSocket") { + t.Fatalf("WebSocket operation must describe its client requirements, not a plain HTTP request: %q %v", command, notes) + } +} func jsonNumber(s string) any { v, e := js.UnmarshalJSON(strings.NewReader(s)) if e != nil { @@ -352,3 +382,48 @@ func TestExternalDynamicReference(t *testing.T) { t.Fatalf("external dynamic reference rejected: %v", d.Diagnostics) } } + +func TestExamplesAreOptionalButValidatedWhenPresent(t *testing.T) { + d := fixture(t, "valid.yaml") + var remove func(any) + remove = func(value any) { + switch v := value.(type) { + case map[string]any: + delete(v, "example") + delete(v, "examples") + for _, child := range v { + remove(child) + } + case []any: + for _, child := range v { + remove(child) + } + } + } + remove(d.Root) + d.validate("") + if len(d.Diagnostics) != 0 { + t.Fatalf("missing examples must be accepted: %+v", d.Diagnostics) + } + invalid := fixture(t, "invalid-example.yaml") + invalid.validate("") + for _, diagnostic := range invalid.Diagnostics { + if diagnostic.Rule == "example" { + return + } + } + t.Fatal("invalid supplied example must still fail") +} + +func TestRequestWithoutExampleUsesBodyFile(t *testing.T) { + op := Object{"method": "POST", "path": "/jobs", "servers": []any{Object{"url": "https://api.test"}}, + "variants": []any{Object{"direction": "Request", "media": "application/json", "required": true, "examples": []any{}}}, + } + command, notes := curlExample(Source{}, op) + if !strings.Contains(command, "--data-binary @request-body") || strings.Contains(command, "--data-raw") { + t.Fatalf("request must use a body file, not an invented payload: %s", command) + } + if !strings.Contains(strings.Join(notes, " "), "Prepare request-body") { + t.Fatalf("missing body instructions: %v", notes) + } +} diff --git a/hack/api-docs/model.go b/hack/api-docs/model.go index 3103b0deb5..9af969d876 100644 --- a/hack/api-docs/model.go +++ b/hack/api-docs/model.go @@ -46,7 +46,7 @@ func (d *Document) model() Object { op["references"] = refs(op["raw"], schemaURLs) op["requestSchema"] = firstRequestSchema(op) } - return Object{"id": d.Source.ID, "product": d.Source.Product, "title": d.Source.Title, "version": obj(d.Root["info"])["version"], "description": obj(d.Root["info"])["description"], "url": route(d.Source.ID), "manual": d.Source.Manual, "guides": d.Source.Guides, "connection": d.Source.Connection, "servers": d.Root["servers"], "securitySchemes": obj(d.Root["components"])["securitySchemes"], "tags": d.Root["tags"], "operations": ops, "schemas": schemas, "schemaURLs": schemaURLs, "digest": d.Digest, "owner": d.Source.Owner, "source": d.Source.Source, "sourceURL": "/" + strings.TrimPrefix(d.Source.Source, "content/"), "diagnostics": d.Diagnostics, "schemaCount": d.SchemaCount, "exampleCount": d.ExampleCount} + return Object{"experimental": d.Source.Experimental, "id": d.Source.ID, "product": d.Source.Product, "title": d.Source.Title, "version": obj(d.Root["info"])["version"], "description": obj(d.Root["info"])["description"], "url": route(d.Source.ID), "manual": d.Source.Manual, "guides": d.Source.Guides, "connection": d.Source.Connection, "servers": d.Root["servers"], "securitySchemes": obj(d.Root["components"])["securitySchemes"], "tags": d.Root["tags"], "operations": ops, "schemas": schemas, "schemaURLs": schemaURLs, "digest": d.Digest, "owner": d.Source.Owner, "source": d.Source.Source, "sourceURL": "/" + strings.TrimPrefix(d.Source.Source, "content/"), "diagnostics": d.Diagnostics, "schemaCount": d.SchemaCount, "exampleCount": d.ExampleCount} } func refs(v any, urls Object) []any { found := map[string]bool{} @@ -114,6 +114,9 @@ func responseMedia(op Object) string { } func curlExample(src Source, op Object) (string, []string) { + if len(obj(obj(op["raw"])["x-websocket"])) > 0 { + return "", []string{"Use a WebSocket client with the subprotocol, credential transport, and frame schemas documented for this operation."} + } notes := []string{} args := []string{"curl"} if op["method"] == "HEAD" { diff --git a/hack/api-docs/sources.json b/hack/api-docs/sources.json index 123004ee1f..4928579a9f 100644 --- a/hack/api-docs/sources.json +++ b/hack/api-docs/sources.json @@ -39,6 +39,20 @@ "manual": "/manuals/ai/sandboxes/governance/", "connection": "hosted", "guides": [] + }, + { + "id": "sandboxes", + "product": "sandboxes", + "title": "Docker Sandboxes", + "source": "content/reference/api/sandboxes/api.yaml", + "owner": "docker/sbx-api", + "manual": "/manuals/ai/sandboxes-api/", + "connection": "hosted", + "experimental": true, + "guides": [ + "/manuals/ai/sandboxes-api/authentication.md", + "/manuals/ai/sandboxes-api/get-started.md" + ] } ], "legacyAPIs": [ diff --git a/hack/sandboxes/README.md b/hack/sandboxes/README.md new file mode 100644 index 0000000000..8c4eba52be --- /dev/null +++ b/hack/sandboxes/README.md @@ -0,0 +1,47 @@ +# Sandboxes documentation import + +The public cookbook export is published to the `generated` branch of +`docker/sbx-api` after its documentation CI passes. `source.json` pins both +the generated commit and the source commit recorded in that export's `SOURCE` +file. The guide list explicitly selects the recipes to publish. + +`name-a-sandbox-and-find-it-again.md` is temporarily excluded pending a +working example. Restore its manifest entry and import it after verification. + +Run the import from the repository root with Python 3 and `gh` authenticated for +`docker/sbx-api`: + +```console +$ python3 hack/sync-sandboxes-docs.py +``` + +The import writes: + +- Cookbook recipes to `content/manuals/ai/sandboxes-api/cookbook/` +- The public OpenAPI specification to + `content/reference/api/sandboxes/api.yaml` + +Treat these files as generated. Make prose, example, and API corrections in +`docker/sbx-api`, then import the updated export. The cookbook's `_index.md` +is maintained in docker/docs, along with the overview, installation, +authentication, concepts, errors, and limits pages. + +The import copies the selected recipes and OpenAPI specification byte for byte. +It excludes the upstream overview, installation page, and generated Markdown +API reference. Recipe installation links point to the canonical Docker Docs +installation page. + +The specification is registered in `hack/api-docs/sources.json`. The shared API +renderer generates an overview and separate operation and schema pages under +`/reference/api/sandboxes/latest/`, in HTML and Markdown. Run +`./hack/api-docs/run.sh generate` before Hugo. Strict validation checks the +specification and its examples. Fix failures upstream and re-import the export. + +To update, change both commit IDs in `source.json`, review additions or removals +in the upstream guide list, and run the import. Removed guides require explicit +deletion and a link check. Review the diff and build the site before committing. +The import does not select the latest release or push changes. + +Release automation can update this manifest and run the same command in a pull +request once upstream publishes a release-to-export mapping. Keep the source +and generated commits paired rather than importing a moving branch. diff --git a/hack/sandboxes/source.json b/hack/sandboxes/source.json new file mode 100644 index 0000000000..fe45736781 --- /dev/null +++ b/hack/sandboxes/source.json @@ -0,0 +1,43 @@ +{ + "repository": "docker/sbx-api", + "generatedCommit": "60d79c1d7744aa5ea5b3cfe871a2faaa5070e7ec", + "sourceCommit": "1a7f40266bc009e9602f3ca8a1cf809e1fc391f5", + "guides": [ + "add-a-docker-credential-for-cloud-sandboxes.md", + "add-tools-with-kits.md", + "attach-persistent-storage.md", + "clone-a-cloud-sandbox.md", + "connect-to-cloud-with-a-bearer-token.md", + "control-what-a-sandbox-can-reach.md", + "copy-a-file-into-a-cloud-sandbox.md", + "create-your-first-sandbox.md", + "delete-a-cloud-sandbox.md", + "expose-a-port-from-a-cloud-sandbox.md", + "find-a-process-you-lost-track-of.md", + "get-a-stored-secret-into-a-sandbox.md", + "get-an-ssh-certificate.md", + "get-image-pull-urls.md", + "give-a-sandbox-an-mcp-gateway.md", + "handle-errors-and-degradation.md", + "keep-a-cloud-sandbox-running.md", + "let-a-stopped-sandbox-resume-on-demand.md", + "manage-cloud-secrets.md", + "manage-network-policies.md", + "page-through-and-filter-lists.md", + "read-files-out-of-a-sandbox.md", + "recover-when-the-endpoint-moves.md", + "register-and-manage-an-image.md", + "retry-without-creating-duplicates.md", + "run-a-complete-example.md", + "run-an-interactive-shell-in-a-cloud-sandbox.md", + "run-something-that-produces-real-output.md", + "run-work-across-many-sandboxes.md", + "run-your-first-command.md", + "run-your-own-container-image.md", + "send-values-the-api-accepts.md", + "snapshot-and-fork-a-sandbox.md", + "stop-and-restart-a-sandbox.md", + "what-your-workload-starts-with.md", + "work-within-the-limits.md" + ] +} diff --git a/hack/sync-sandboxes-docs.py b/hack/sync-sandboxes-docs.py new file mode 100644 index 0000000000..02885d2a0d --- /dev/null +++ b/hack/sync-sandboxes-docs.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +"""Import the pinned public cookbook export. See hack/sandboxes/README.md.""" + +import io +import json +from pathlib import Path +import subprocess +import tarfile + + +ROOT = Path(__file__).resolve().parent.parent +MANIFEST = ROOT / "hack/sandboxes/source.json" +GUIDES = ROOT / "content/manuals/ai/sandboxes-api/cookbook" +REFERENCE = ROOT / "content/reference/api/sandboxes" + + +def main(): + source = json.loads(MANIFEST.read_text()) + archive = subprocess.check_output([ + "gh", "api", + f"repos/{source['repository']}/tarball/{source['generatedCommit']}", + ]) + with tarfile.open(fileobj=io.BytesIO(archive), mode="r:gz") as tar: + files = {} + for member in tar.getmembers(): + if member.isfile(): + path = member.name.split("/", 1)[1] + files[path] = tar.extractfile(member).read() + + if files["SOURCE"].decode().strip() != source["sourceCommit"]: + raise SystemExit("The generated export does not match the recorded source commit") + + # Prepare every output before changing the working tree. Fail if an upstream + # page disappears instead of silently dropping a published recipe. + outputs = {} + for name in source["guides"]: + if Path(name).name != name or not name.endswith(".md"): + raise SystemExit(f"Invalid guide filename: {name}") + outputs[GUIDES / name] = files[f"cookbook/outputs/guides/{name}"] + + outputs[REFERENCE / "api.yaml"] = files["cookbook/outputs/api-reference/api.yaml"] + + for destination, content in outputs.items(): + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_bytes(content) + print(f"Imported {len(source['guides'])} recipes and the public OpenAPI specification") + + +if __name__ == "__main__": + main() diff --git a/layouts/_partials/api-reference/manual-link.html b/layouts/_partials/api-reference/manual-link.html index 0b92028d75..a535e25e53 100644 --- a/layouts/_partials/api-reference/manual-link.html +++ b/layouts/_partials/api-reference/manual-link.html @@ -1,11 +1,13 @@ -{{ $data := index hugo.Data "api-reference" }} -{{ range $data.apis }} - {{ $manual := site.GetPage .manual }} - {{ if and $manual (eq $manual.RelPermalink $.RelPermalink) }} -

- Explore the {{ .title }} API {{ .version }} → -

+{{ if ne .Params.apiReferenceBanner false }} + {{ $data := index hugo.Data "api-reference" }} + {{ range $data.apis }} + {{ $manual := site.GetPage .manual }} + {{ if and $manual (eq $manual.RelPermalink $.RelPermalink) }} +

+ Explore the {{ .title }} API {{ .version }} → +

+ {{ end }} {{ end }} {{ end }} diff --git a/layouts/_partials/api-reference/websocket.html b/layouts/_partials/api-reference/websocket.html new file mode 100644 index 0000000000..4f6f9a425e --- /dev/null +++ b/layouts/_partials/api-reference/websocket.html @@ -0,0 +1,19 @@ +{{ $socket := .socket }} +{{ $api := .api }} +
+

WebSocket session

+

Subprotocol: {{ $socket.subprotocol }}

+ {{ range $key := slice "authentication" "browser" "reconnect" "terminalFrame" }} + {{ with index $socket $key }}

{{ . }}

{{ end }} + {{ end }} + {{ range $frame := slice (dict "key" "requestFrame" "title" "Client frames") (dict "key" "responseFrame" "title" "Server frames") (dict "key" "errorFrame" "title" "Error frames") }} + {{ with index $socket $frame.key }} +

{{ $frame.title }}

+ {{ partial "api-reference/schema.html" (dict "schema" . "api" $api "depth" 0) }} + {{ end }} + {{ end }} +
+ Handshake, resume fields, and close codes +
{{ $socket | jsonify (dict "indent" "  ") }}
+
+
diff --git a/layouts/_partials/content-default.html b/layouts/_partials/content-default.html index 73580a30d5..4dcb550f63 100644 --- a/layouts/_partials/content-default.html +++ b/layouts/_partials/content-default.html @@ -13,6 +13,9 @@
{{ partial "api-reference/manual-link.html" . }} +{{ if and .CurrentSection.Params.recipeCatalog (not .IsSection) }} +

Browse all recipes

+{{ end }} {{ .Content }} {{- if and (eq .Type "guides") (not .IsSection) (ne .CurrentSection .FirstSection) -}} {{- with .PrevInSection -}} diff --git a/layouts/_partials/sidebar/sections.html b/layouts/_partials/sidebar/sections.html index 0bd06b5dcd..8dc3433b6b 100644 --- a/layouts/_partials/sidebar/sections.html +++ b/layouts/_partials/sidebar/sections.html @@ -24,11 +24,14 @@ {{- $pages := slice }} {{- range $sourcePages }} {{- $isActive := or (eq page .) (page.IsDescendant .) }} - {{- if or (ne .Params.sitemap false) $isActive $revealHidden }} + {{- if and (or (not $section.Params.sidebar.activeChildrenOnly) $isActive) (or (ne .Params.sitemap false) $isActive $revealHidden) }} {{- $pages = $pages | append . }} {{- end }} {{- end }} {{- $ungrouped := where $pages "Params.sidebar.group" "==" nil }} + {{- if $section.Params.sidebar.activeChildrenOnly }} + {{- $ungrouped = $pages }} + {{- end }} {{- range $ungrouped }} {{- if and .IsSection (not .Params.apiID) }} {{- template "renderList" (dict "section" . "revealHidden" $revealHidden) }} @@ -36,7 +39,11 @@ {{- template "renderSingle" . }} {{- end }} {{- end }} - {{- range $section.Params.sidebar.groups }} + {{- $groups := $section.Params.sidebar.groups }} + {{- if $section.Params.sidebar.activeChildrenOnly }} + {{- $groups = slice }} + {{- end }} + {{- range $groups }}