# Agent capabilities Source: https://docs.opencomputer.dev/agents/capabilities Choose between tools, MCP servers, skills, subagents, and outbound connections Capabilities give an agent something beyond its instructions and model. Choose the narrowest capability that fits the job. | Capability | Use it for | Where the work happens | How it is attached | | ----------------- | ------------------------------------------------------ | ----------------------------------------- | ----------------------------------------------- | | Code-defined tool | One typed operation you implement | Your TypeScript `run()` function | `useTool(tool)` | | MCP server | A service that already publishes a collection of tools | The remote MCP server | `useMcpServer(server)` | | Skill | A reusable procedure, checklist, or supporting files | The agent follows the loaded instructions | Place it under `skills/` | | Subagent | Delegating a focused task to another project agent | The delegated agent | `useSubagent(agentId)` | | HTTP connection | A constrained authenticated outbound request | OpenComputer sends the declared request | Call `defineConnection().fetch()` inside a tool | `useModel()` selects the model for a render, but it does not add an executable capability. ## Tool or MCP server? Use a [code-defined tool](/agents/tools) when you own the operation, want a small schema, or need application-specific validation. Use an [MCP server](/agents/mcp) when another service already hosts the tools. Both ultimately give the model callable tools. A code-defined tool exposes the single operation you register; an MCP server exposes the tools it advertises. ## Tool or skill? A tool performs an operation and returns structured data. A [skill](/agents/skills) teaches the agent how to approach a category of work. Many agents use both: the skill describes the workflow while tools perform the individual actions. ## Tool or subagent? Use a tool for a bounded operation such as fetching an order. Use a [subagent](/agents/subagents) when the task benefits from its own instructions, model selection, capabilities, and working context. ## Connections are not managed accounts `defineConnection()` declares a secret-backed HTTP destination. It is not an OAuth account selector and does not itself give the model a tool. Call it from a code-defined tool, or attach it to an MCP definition when that server accepts the corresponding header-based authorization. See [Secrets and outbound requests](/agents/secrets) for the complete security model. # Deployments and environments Source: https://docs.opencomputer.dev/agents/deployments Understand development, production, immutable deployments, and aliases A deployment is an immutable build of one agent. An alias gives a stable name to the deployment clients should run. Every project starts with two working environments: | Environment | Alias | How it changes | | ----------- | ------------- | -------------------------------------- | | Development | `development` | `npm run dev` publishes source changes | | Production | `production` | An explicit deploy advances it | Sessions keep the deployment they started with. Publishing another version does not change the code underneath an existing session. ## Deploy to production From the project directory: ```bash theme={null} npm run deploy -- --alias production ``` The CLI builds the current source, creates an immutable deployment, and moves the `production` alias to it. Development remains available for further work. ## Select an agent and environment Clients identify an agent with `agent-id@alias`: ```text theme={null} support@development support@production ``` The project dashboard's environment selector changes which deployment you are inspecting. The playground has a separate agent selector when a project contains more than one agent. ## Review history Open the project's **Deployments** page to inspect immutable versions, their aliases, and creation times. Use the deployment ID when correlating a session with [logs](/agents/logs) or the [debug inspector](/agents/playground). # Cloud development Source: https://docs.opencomputer.dev/agents/development Sync agent code to Development while your application runs locally OpenComputer's development loop keeps agent execution in the cloud while your source files and optional React application stay on your machine. ```bash theme={null} npm run dev ``` The command performs four jobs: 1. Links the source directory to a cloud project. 2. Builds and publishes every configured agent to `development`. 3. Watches `opencomputer/` and publishes changes automatically. 4. Starts Vite when the project includes the React application. It also prints the project dashboard URL so you can test the same development deployment in the [agent playground](/agents/playground). ## Select a project On the first run, select an existing project or create one. The choice is saved locally and reused by later commands. ```bash theme={null} npx opencomputer link ``` Use an explicit project in scripts or other non-interactive environments: ```bash theme={null} npx opencomputer dev --project npx opencomputer dev --create-project "Support agents" ``` ## Work with the React application When a React application is present, the same `npm run dev` process starts both cloud synchronization and Vite. Keep it running while using the application; the development bridge exists only for the lifetime of that process. New sessions use the latest synchronized development deployment. Existing sessions keep the deployment they started with, so create a new session when testing a source change. ## Synchronize development secrets Put agent credentials in `opencomputer/.env.local`. OpenComputer considers only names referenced by `useSecret()` and infers their allowed destinations from `defineConnection()` declarations. ```dotenv theme={null} GITHUB_TOKEN=github_pat_... ``` The CLI asks before the first upload and then watches for changes. Unreferenced variables are skipped, and removing a local value does not delete its cloud counterpart. See [Secrets and outbound requests](/agents/secrets). ## Development and production stay separate Saving source advances only `development`. Production remains on its current immutable deployment until you explicitly deploy it. See [Deployments and environments](/agents/deployments). # Exa research session Source: https://docs.opencomputer.dev/agents/examples/exa-research-session A web-research agent with live Exa results and durable follow-up questions The Exa research starter is a small React application backed by an OpenComputer agent. Ask a question in the browser and the agent searches the live web with Exa, compares relevant results, and streams a concise answer with source links. Follow-up questions reuse the same session, so the agent keeps the research conversation and can refine an earlier answer without starting over. ## How it works 1. The React application sends a question to an OpenComputer session. 2. The agent uses its `exa_search` tool when the question needs current web research. 3. The declared Exa connection sends the request with the write-only `EXA_API_KEY` without adding it to agent source or browser code. 4. The agent reads the results and streams an answer with clickable source URLs back to the application. The starter also includes an agent-specific skill that guides the research workflow and uses `@opencomputer/react` to stream messages and continue the same session. ## Start with the example Clone the complete React and OpenComputer project from GitHub. The repository README covers installation, setting the Exa secret, running the combined cloud-agent and React development process, and deploying the finished application. # Agent hooks Source: https://docs.opencomputer.dev/agents/hooks Compose agent capabilities with reactive hooks An agent function returns instructions. Hooks give that agent the model, tools, services, and context it needs to follow those instructions. ```tsx theme={null} import { useInput, useModel, useTool } from "@opencomputer/agent"; export default function Agent() { const input = useInput(); useModel("anthropic/claude-sonnet-4.6"); if (input.text?.includes("latest")) { useTool("web-search"); } return "Answer directly. Use current sources when web search is available."; } ``` ## How hooks work OpenComputer renders the exported function before each model step. Every hook called during that render contributes to the next step's configuration. Hooks describe capabilities; they do not call the model or run a tool themselves. Unlike React component hooks, OpenComputer resource hooks can be conditional. The example above exposes `web-search` only when the current input asks for recent information. Hooks can only run while OpenComputer is rendering an agent. Keep network requests and other asynchronous work inside [tools](/agents/tools), not in the agent function. ## Built-in hooks | Hook | What it does | Guide | | ------------------------ | ------------------------------------------------------------ | ------------------------------------ | | `useInput()` | Reads the current input source, text, and structured payload | [Inputs](/agents/inputs) | | `useCurrentInput()` | Alias for `useInput()` | [Inputs](/agents/inputs) | | `useModel(model)` | Selects the model used for the next step | [Models](/agents/models) | | `useTool(tool)` | Exposes a named or code-defined tool | [Tools](/agents/tools) | | `useMcpServer(server)` | Exposes tools from a managed HTTPS MCP server | [MCP servers](/agents/mcp) | | `useSubagent(agent)` | Makes another project agent available for delegation | [Subagents](/agents/subagents) | | `useSessionData(key)` | Reads a durable value associated with the session | [Session data](/agents/session-data) | [Skills](/agents/skills) are also agent capabilities, but they are discovered from the agent's `skills/` directory rather than attached with a hook. ## Compose your own hooks Hooks are ordinary synchronous functions. Group related capabilities in a custom hook when several agents use the same setup: ```tsx theme={null} import { useTool } from "@opencomputer/agent"; function useGitHubResearch() { useTool("github-search"); return "Check GitHub before making claims about repositories or issues."; } export default function Agent() { const githubInstructions = useGitHubResearch(); return `Research the request carefully. ${githubInstructions}`; } ``` Custom hooks keep agent functions readable and make capability bundles reusable. Prefix them with `use` so their purpose is obvious. ## Next steps * [Read and route inputs](/agents/inputs) * [Choose a model](/agents/models) * [Give the agent tools](/agents/tools) * [Connect an MCP server](/agents/mcp) * [Add reusable skills](/agents/skills) # Inputs Source: https://docs.opencomputer.dev/agents/inputs Read text, structured payloads, and input sources with useInput `useInput()` reads the input that caused the current agent render. Use it to include the current request in the instructions or change behavior based on where the request came from. ```tsx theme={null} import { useInput } from "@opencomputer/agent"; export default function Agent() { const input = useInput(); return input.text ? `Help the user with this request: ${input.text}` : "Help the user with their request."; } ``` `useCurrentInput()` is an alias for `useInput()`. ## Input shape ```ts theme={null} interface AgentInput { readonly source: InputSource; readonly text?: string; readonly payload?: DataValue; } ``` | Field | Meaning | | --------- | ----------------------------------------------------- | | `source` | How the work entered the session | | `text` | Optional human-readable message | | `payload` | Optional structured JSON value supplied by the source | `DataValue` means any JSON-compatible value: `null`, a boolean, number, string, array, or object. Both `text` and `payload` are optional, so check them before use. ## Adapt to the source The current direct and delegated flows can share one agent definition: ```tsx theme={null} import { useInput } from "@opencomputer/agent"; export default function Agent() { const input = useInput(); return input.source === "subagent" ? "Complete the delegated task and return a concise result." : `Help with: ${input.text ?? "the current request"}`; } ``` ## Read structured payloads Narrow or validate a payload before reading application-specific fields: ```tsx theme={null} import { useInput } from "@opencomputer/agent"; type ResearchRequest = { topic?: string; depth?: "brief" | "deep"; }; export default function Agent() { const input = useInput(); const request = (input.payload ?? {}) as ResearchRequest; if (request.topic) { return `Research ${request.topic} at ${request.depth ?? "brief"} depth.`; } return "Ask for a research topic before starting."; } ``` Validate untrusted payloads in a tool before performing side effects. ## Input is not a prompt UI `useInput()` only reads admitted input. It does not pause the session to ask a person a question. The durable conversation and later messages are managed by the [session](/agents/sessions). Input payload also differs from [session data](/agents/session-data): a payload belongs to the current input, while session data can persist across renders. # Logs Source: https://docs.opencomputer.dev/agents/logs Inspect agent output and outbound-request activity from the OpenComputer CLI OpenComputer collects agent output together with outbound-request events. Logs are indexed by project, environment, agent, and session so you can inspect failures from the project or CLI. ## Read the current agent's logs From an initialized project: ```bash theme={null} npx opencomputer logs ``` The CLI uses the local project binding and current agent automatically. Follow new entries while developing: ```bash theme={null} npx opencomputer logs --follow ``` ## Filter logs ```bash theme={null} npx opencomputer logs --agent hello-world --environment development npx opencomputer logs --session npx opencomputer logs --limit 200 ``` Use JSON Lines for scripts and log processors: ```bash theme={null} npx opencomputer logs --follow --json ``` Each entry includes a stable cursor, timestamp, level, source, message, and the applicable agent or session identifiers. ## Secret safety Outbound-request logs describe the connection, method, destination path, status, and timing. They do not contain injected secret values or secret-bearing headers. Request headers that could override managed credentials are discarded before the outbound request is sent. # MCP servers Source: https://docs.opencomputer.dev/agents/mcp Attach managed HTTPS MCP servers with useMcpServer MCP (Model Context Protocol) connects an agent to tools hosted by an external service. Declare the server once with `defineMcpServer()`, then attach it to an agent render with `useMcpServer()`. ## Connect a server ```tsx theme={null} import { defineMcpServer, useMcpServer, useModel, } from "@opencomputer/agent"; const docs = defineMcpServer({ id: "company-docs", url: "https://mcp.example.com/server", }); export default function Agent() { useModel("anthropic/claude-sonnet-4.6"); useMcpServer(docs); return "Answer questions using the company documentation when relevant."; } ``` Managed MCP URLs must use HTTPS. OpenComputer connects to the server and makes its tools available to the managed agent loop; agent code does not open or close the connection itself. ## Definition fields | Field | Required | Purpose | | ------------ | -------- | ---------------------------------------------------------- | | `id` | yes | Stable ID for the server in the deployment | | `url` | yes | HTTPS MCP endpoint | | `connection` | no | Secret-backed HTTP connection used to authorize the server | `useMcpServer()` accepts either a definition or an existing server ID. ## Attach authorization Reference a secret-backed HTTP connection when the server requires authorization: ```tsx theme={null} import { bearer, defineConnection, defineMcpServer, useSecret, useMcpServer, } from "@opencomputer/agent"; const github = defineConnection({ id: "github-mcp-auth", origin: "https://api.githubcopilot.com", headers: { Authorization: bearer(useSecret("GITHUB_PAT")), }, }); const githubMcp = defineMcpServer({ id: "github", url: "https://api.githubcopilot.com/mcp/", connection: github, }); export default function Agent() { useMcpServer(githubMcp); return "Use GitHub tools to investigate repository questions."; } ``` The declaration contains only a secret reference. Set the secret with `opencomputer secrets set GITHUB_PAT`; its value is injected only into requests to the declared origin. See [Secrets and outbound requests](/agents/secrets). ## Attach MCP conditionally An agent can expose a server only when the current request needs it: ```tsx theme={null} import { useInput, useMcpServer } from "@opencomputer/agent"; export default function Agent() { const input = useInput(); if (input.text?.toLowerCase().includes("documentation")) { useMcpServer(docs); } return "Use company docs when that capability is available."; } ``` Conditional attachment reduces the number of tools the model must choose from on unrelated requests. ## MCP versus code-defined tools Use [code-defined tools](/agents/tools) when you own the operation or need custom validation and control. Use MCP when a service already publishes the tools you need or when several agents should consume the same remote tool set. # Models Source: https://docs.opencomputer.dev/agents/models Select a managed model with useModel `useModel()` selects the model that powers the next agent step. OpenComputer handles provider authentication and response streaming. ```tsx theme={null} import { useModel } from "@opencomputer/agent"; export default function Agent() { useModel("anthropic/claude-sonnet-4.6"); return "You are a helpful assistant. Be concise and practical."; } ``` `useModel()` is a declaration. It returns nothing and does not make a model request itself. ## Model identifiers Use a `provider/model` string: ```tsx theme={null} useModel("anthropic/claude-sonnet-4.6"); ``` The object form is equivalent: ```tsx theme={null} useModel({ provider: "anthropic", model: "claude-sonnet-4.6", }); ``` Keep model selection in agent code so development and immutable deployments record the behavior together. Provider credentials are not added to source bundles or client applications. ## Select a model conditionally Because the agent re-renders for each model step, selection can respond to the current input: ```tsx theme={null} import { useInput, useModel } from "@opencomputer/agent"; export default function Agent() { const input = useInput(); const needsDeepReview = input.text?.includes("deep review") ?? false; useModel( needsDeepReview ? "anthropic/claude-sonnet-4.6" : "anthropic/claude-haiku-4.5", ); return needsDeepReview ? "Review the request carefully and explain important tradeoffs." : "Answer directly and briefly."; } ``` Select one model for the next step and keep the choice predictable: model changes affect cost, latency, context limits, and output behavior. ## Keep credentials out of agent code Do not place provider API keys in `agent.ts`, tools, prompts, or the React application. OpenComputer authenticates the selected provider without adding the credential to agent code. For credentials used by your own HTTP tools instead of model inference, use [Secrets and outbound requests](/agents/secrets). # Serverless Agents Source: https://docs.opencomputer.dev/agents/overview Define reactive agents in TypeScript and run them in the OpenComputer cloud OpenComputer is a managed runtime for agents defined as code. A project can contain several agents and optionally a React application that talks to them. You write the behavior; OpenComputer manages development deployments, model execution, tools, durable sessions, streaming, and production versions. ```tsx theme={null} import { useInput, useModel } from "@opencomputer/agent"; export default function Agent() { const input = useInput(); useModel("anthropic/claude-sonnet-4.6"); return input.text ? `Help the user with this request: ${input.text}` : "You are a helpful assistant."; } ``` The exported function is a reactive description of the agent. OpenComputer renders it for the current input and uses its return value as instructions for the managed agent loop. Hooks attach the model, tools, MCP servers, and subagents needed for that render. Group multiple agents and a client application in one repository. Build agent behavior with a function and conditional hooks. Sync source directly to a managed development environment. Stream responses and inspect durable conversations. Choose between tools, MCP servers, skills, and subagents. Declare secret-backed connections without exposing credentials to agents. Follow agent and outbound-request activity from the CLI. ## Start a project ```bash theme={null} npm create @opencomputer/start@latest my-agent cd my-agent npm install npx opencomputer login npm run dev ``` The initializer asks whether to include a React SPA. The first `npm run dev` lets you create a cloud project or select an existing one, prints its dashboard URL, and syncs every configured agent to `development`. When a SPA exists, the same command also starts it locally. There is no local agent server. An optional React app runs locally, while agent code runs in OpenComputer's managed development cloud. ## What is available today * TypeScript projects containing one or more agents * reactive instructions, model selection, tools, subagents, MCP servers, input metadata, and session data reads * cloud development with file watching and automatic synchronization * an optional React client using `@opencomputer/react` for streaming and multi-turn session reuse * dashboard and CLI playground sessions * immutable deployments promoted through aliases such as `production` * project-level secrets with optional agent overrides and constrained outbound requests * indexed runtime and egress logs available from the CLI Agent behavior belongs in the repository under `opencomputer/`. The dashboard is primarily for selecting environments, testing agents, and inspecting what was deployed. # Playground and debugging Source: https://docs.opencomputer.dev/agents/playground Test deployments and inspect what the agent received for each turn The agent playground is the fastest way to test a cloud deployment without building a client. Open a project, choose an environment and agent, then create a session. ## Select the correct target The project header selects `development` or `production`. Projects with multiple agents have a separate agent selector in the playground. A session keeps the deployment selected when it starts. Create a new session after changing the target when you want an isolated test. Resume an existing session when you want to verify multi-turn behavior. ## Use the debug inspector The inspector shows the information needed to explain a turn: * deployment and session identifiers * rendered agent instructions * selected model * tools and MCP capabilities available to that render * tool activity and errors OpenComputer's private platform instructions and credentials are intentionally not shown. The inspector describes your deployed agent configuration, not the internal execution implementation. ## Follow runtime logs Use the CLI when a failure needs more detail: ```bash theme={null} npx opencomputer logs --follow npx opencomputer logs --session ``` See [Logs](/agents/logs) for filters and JSON output. # Projects and agents Source: https://docs.opencomputer.dev/agents/projects Organize multiple cloud agents and a React application A project is the cloud boundary for related agents, environments, deployments, and sessions. A source repository contains the project's agent definitions and the application that interacts with them. ## Project structure The starter is intentionally small: ```text theme={null} my-agent/ ├── opencomputer/ │ ├── .env.example │ ├── project.ts │ └── agents/ │ └── hello-world/ │ ├── agent.ts │ ├── tools/ # optional │ └── skills/ # optional ├── src/ │ ├── App.tsx │ └── main.tsx ├── package.json └── vite.config.ts ``` * `opencomputer/project.ts` declares project metadata and agent IDs. * `opencomputer/agents//agent.ts` defines one agent. * `opencomputer/.env.local` optionally holds ignored development secrets. * `src/` is a normal React application. * `.opencomputer/` is ignored local state created by the CLI. Add source files only when the project uses them. The starter does not generate placeholder capability directories. ## Create or select the cloud project The source scaffold and cloud project are separate. `npm create` writes local source. Link it to a cloud project with: ```bash theme={null} npx opencomputer link ``` The command asks whether to create a project or select an existing one. If you skip it, the first project-scoped command shows the same prompt. For non-interactive use: ```bash theme={null} npx opencomputer dev --project npx opencomputer dev --create-project "Support agents" ``` Later commands reuse `.opencomputer/project.json`. Run `opencomputer link` again when you intentionally want this source directory to target another project. ## Add another agent Create a new agent module: ```text theme={null} opencomputer/agents/researcher/agent.ts ``` ```tsx theme={null} import { useModel } from "@opencomputer/agent"; export default function Agent() { useModel("anthropic/claude-sonnet-4.6"); return "Research the request, verify claims, and cite useful sources."; } ``` Then list it in `opencomputer/project.ts`: ```tsx theme={null} export default { name: "Customer workspace", agents: ["hello-world", "researcher"], }; ``` The running development process synchronizes both agents. In the dashboard, the project-level environment selector switches between `development` and `production`; the playground has a separate agent selector. ## Inspect the project The current project dashboard provides: * **Agent playground** for creating and resuming test sessions * **Deployments** for immutable version history and active aliases * **Sessions** created through the dashboard, React client, or API * **Secrets** for write-only project and agent credentials Project and agent behavior remains code-owned. The dashboard is for selection, inspection, and testing. Use `opencomputer logs` to diagnose agent and outbound-request failures. # Quickstart Source: https://docs.opencomputer.dev/agents/quickstart Create an agent project and sync it to Development (Cloud) ## Prerequisites * Node.js 22 or newer * an OpenComputer account ## 1. Create the app ```bash theme={null} npm create @opencomputer/start@latest my-agent cd my-agent npm install ``` Use `.` instead of `my-agent` to initialize an empty current directory. The initializer asks whether to create agent code only or include a React SPA. ## 2. Log in ```bash theme={null} npx opencomputer login ``` The CLI opens the OpenComputer device-login flow and stores the login locally. The production API at `https://app.opencomputer.dev` is used by default. ## 3. Start cloud development ```bash theme={null} npm run dev ``` On the first run, choose an existing cloud project or create a new one. The process prints the project dashboard URL, watches `opencomputer/`, and syncs changes to `development`. If you included the SPA, the same command starts Vite; open its URL and send a message. Keep `npm run dev` running while using the React app. The authenticated development bridge exists only for the lifetime of that process. ## 4. Change the agent Edit `opencomputer/agents/hello-world/agent.ts`: ```tsx theme={null} import { useInput, useModel } from "@opencomputer/agent"; export default function Agent() { const input = useInput(); useModel("anthropic/claude-sonnet-4.6"); return `Be concise and practical. Current request: ${input.text ?? "none"}`; } ``` Saving the file publishes a new development deployment. Start a new playground or client session to test it; an existing session keeps the deployment it started with. See [Cloud development](/agents/development) for project selection, automatic secret synchronization, and the full development lifecycle. ## 5. Deploy to production ```bash theme={null} npm run deploy -- --alias production ``` The command builds an immutable deployment and advances the `production` alias. Development remains available for subsequent edits. See [Deployments and environments](/agents/deployments) for aliases and version history. Add more agents and understand project bindings. Add models, tools, MCP servers, and subagents. # React integration Source: https://docs.opencomputer.dev/agents/react Stream agent sessions from a React application with useAgent Projects created with the React option use `@opencomputer/react`. The package owns session creation, multi-turn reuse, and streamed assistant messages. ```tsx theme={null} import { useAgent } from "@opencomputer/react"; export default function Chat() { const { messages, send, sessionId, isRunning, error } = useAgent( "hello-world@development", ); return (
{messages.map((message) => (

{message.role}: {message.text}

))} {sessionId ? Session: {sessionId} : null} {error ?

{error}

: null}
); } ``` ## Hook result | Value | Purpose | | ------------ | ------------------------------------------------------------- | | `messages` | User and assistant messages accumulated by this hook instance | | `send(text)` | Starts or continues the session and streams the response | | `sessionId` | Current cloud session after the first message | | `isRunning` | Whether a turn is active | | `error` | Most recent request error | ## Select the target Pass `agent-id@alias` to choose the agent and environment: ```tsx theme={null} useAgent("support@development"); useAgent("support@production"); ``` An options object can also set the input source, API base path, or fetch implementation: ```tsx theme={null} useAgent({ agent: "support@development", source: "customer-portal", }); ``` ## Development Keep `npm run dev` running while using the local application. It starts Vite and provides the authenticated development bridge; credentials are not bundled into browser code. For the durable conversation model, see [Sessions and turns](/agents/sessions). # Reactive agents Source: https://docs.opencomputer.dev/agents/reactive-agents Render instructions and capabilities from the current input An agent is a synchronous TypeScript function that returns instructions. The OpenComputer runtime owns the durable conversation, streaming model calls, tool loop, and continuation. Your function calculates the agent configuration for the current render. ```tsx theme={null} export default function Agent() { return "You are a helpful assistant."; } ``` This keeps ordinary TypeScript control flow available. Hooks may be called conditionally, so a capability can be attached only when the current request needs it. ## Hooks | Hook | Current value or effect | | ---------------------- | --------------------------------------------------------------- | | `useInput()` | Reads the current source, text, and optional structured payload | | `useCurrentInput()` | Alias for `useInput()` | | `useModel(model)` | Selects a model using `provider/model` notation | | `useTool(tool)` | Makes a named or code-defined tool available | | `useSubagent(agent)` | Makes another project agent available as a subagent | | `useMcpServer(server)` | Attaches a managed HTTPS MCP server | | `useSessionData(key)` | Reads a value previously stored in the current session | `useInput()` is not a human-in-the-loop prompt. It describes the input that caused the current render. See [Inputs](/agents/inputs) for its text, payload, and source metadata. ## Conditional configuration ```tsx theme={null} import { useInput, useModel, useSubagent, useTool, } from "@opencomputer/agent"; export default function Agent() { const input = useInput(); useModel("anthropic/claude-sonnet-4.6"); if (input.text?.includes("research")) { useTool("web-search"); useSubagent("researcher"); } return "Answer directly. Research and verify when those capabilities exist."; } ``` The hook calls describe capabilities for the next model step; they do not run the model or tool themselves. ## Define a tool Tools are TypeScript values with JSON Schema inputs and an asynchronous or synchronous `run` function. Put the implementation beside the agent, for example at `opencomputer/agents/hello-world/tools/hacker-news.ts`: ```tsx theme={null} import { defineTool } from "@opencomputer/agent"; export const latestStories = defineTool({ name: "latest_hacker_news_stories", description: "Fetch the current top Hacker News stories", input: { type: "object", properties: { limit: { type: "number" } }, }, async run({ input, signal, reportProgress }) { await reportProgress({ status: "fetching" }); const limit = Number(input.limit ?? 10); const ids = await fetch( "https://hacker-news.firebaseio.com/v0/topstories.json", { signal }, ).then((response) => response.json() as Promise); return ids.slice(0, limit); }, }); ``` Import and attach it from `agent.ts`: ```tsx theme={null} import { useTool } from "@opencomputer/agent"; import { latestStories } from "./tools/hacker-news"; export default function Agent() { useTool(latestStories); return "Use the Hacker News tool when the user asks for current stories."; } ``` Tool names may contain letters, numbers, underscores, and hyphens. Tool code runs in the managed agent runtime, not in the browser. ## Skills Skills are reusable instruction bundles. Add a skill only when an agent needs one: ```text theme={null} opencomputer/agents/hello-world/skills/code-review/SKILL.md ``` Skills in an agent's `skills/` directory are packaged with that agent and can be loaded by the runtime when their description matches the task. Unlike tools, skills provide instructions and supporting resources rather than a callable JSON Schema function. ## Models Use `provider/model` strings: ```tsx theme={null} useModel("anthropic/claude-sonnet-4.6"); ``` An object form is also supported: ```tsx theme={null} useModel({ provider: "anthropic", model: "claude-sonnet-4.6" }); ``` Provider credentials are managed by OpenComputer and are not placed in agent source or the React application. Continue with [Agent hooks](/agents/hooks), then use the focused guides for [inputs](/agents/inputs), [models](/agents/models), [tools](/agents/tools), [MCP servers](/agents/mcp), [skills](/agents/skills), [subagents](/agents/subagents), and [session data](/agents/session-data). # Secrets and outbound requests Source: https://docs.opencomputer.dev/agents/secrets Use project and agent secrets without exposing values to agent runtimes OpenComputer secrets are write-only values used by declared outbound connections. Plaintext values are not included in source bundles, prompts, deployment manifests, runtime environment variables, logs, or API responses. ## Declare a connection ```tsx theme={null} import { bearer, defineConnection, useSecret, } from "@opencomputer/agent"; const github = defineConnection({ id: "github-api", origin: "https://api.github.com", methods: ["GET"], pathPrefix: "/repos/", headers: { Authorization: bearer(useSecret("GITHUB_TOKEN")), }, }); export default function Agent() { return "Use GitHub when it helps answer the request."; } ``` The origin must use HTTPS. OpenComputer rejects hard-coded sensitive headers such as `Authorization`, `Cookie`, and `X-API-Key`; reference a managed secret instead. Tools can make requests through the same connection: ```tsx theme={null} const response = await github.fetch("/repos/opencomputer/example"); const repository = await response.json(); ``` Only a relative path is accepted. OpenComputer checks the declared origin, path prefix, method, agent, and environment before sending the outbound request. ## Synchronize development secrets Place development-only values in `opencomputer/.env.local`: ```dotenv theme={null} GITHUB_TOKEN=github_pat_... ``` When `npm run dev` starts, the CLI considers only names referenced by `useSecret()` in a `defineConnection()` declaration. It infers the allowed origins from those declarations and asks before uploading each newly discovered secret. Changed values are synchronized while the development process is running. Variables without a matching declaration are skipped. OpenComputer never grants an unmatched value access to every host, and removing a local variable does not delete its cloud value. Use `opencomputer secrets remove` when deletion is intentional. The starter ignores `opencomputer/.env.local` and includes an `opencomputer/.env.example` file for documenting required names without values. ## Set a project secret Secrets belong to a cloud project. If the app is not linked yet, this command first asks you to select or create one, then continues setting the secret. ```bash theme={null} npx opencomputer secrets set GITHUB_TOKEN ``` The CLI reads the value from a hidden prompt. When run from an initialized project it can infer allowed origins from connections that reference the secret. For CI, provide the value through standard input rather than a command argument. Project secrets are available to declared connections in every agent in the project. Create an agent-specific override when one agent needs a different credential: ```bash theme={null} npx opencomputer secrets set GITHUB_TOKEN --agent current ``` Secrets are separate for `development` and `production`: ```bash theme={null} npx opencomputer secrets set GITHUB_TOKEN --environment production npx opencomputer secrets list --environment development npx opencomputer secrets remove GITHUB_TOKEN --environment development ``` List output contains metadata such as the name, scope, environment, and allowed origins. Secret values are never returned. ## Security guarantees OpenComputer resolves the declared connection and scoped secret for each request. The credential is attached only after the destination, method, path, agent, project, and environment have been validated. The value is never added to the agent's source bundle, prompt, browser application, or logs. # Session data Source: https://docs.opencomputer.dev/agents/session-data Read durable per-session values with useSessionData `useSessionData()` reads structured data associated with the current durable session. Use it for application context that should influence later renders, such as a campaign name, locale, audience, or workflow phase. ```tsx theme={null} import { useSessionData } from "@opencomputer/agent"; export default function Agent() { const campaign = useSessionData("campaign_name"); const audience = useSessionData("audience"); return [ "Help plan the campaign.", campaign ? `Campaign: ${campaign}.` : undefined, audience ? `Audience: ${audience}.` : undefined, ] .filter(Boolean) .join(" "); } ``` ## Read a value The key is a string. Pass a TypeScript type argument for the expected value: ```tsx theme={null} const locale = useSessionData("locale"); const approved = useSessionData("brief_approved"); const tags = useSessionData("tags"); ``` The hook returns `undefined` when the key is absent. Handle that case rather than assuming every session has been initialized. Values must be JSON-compatible: `null`, booleans, numbers, strings, arrays, and objects containing those values. ## Configure capabilities from session data Because session data is available during render, it can control instructions and resource hooks: ```tsx theme={null} import { useSessionData, useTool } from "@opencomputer/agent"; import { publishCampaign } from "./tools/publish-campaign"; export default function Agent() { const approved = useSessionData("brief_approved") ?? false; if (approved) { useTool(publishCampaign); } return approved ? "The brief is approved. Publishing is available when the user asks." : "Draft and revise the brief. Do not publish it yet."; } ``` ## Session data versus input `useInput().payload` belongs to the current admitted input. Session data is associated with the durable session and can be read again on later renders. Use input for the event that just arrived and session data for context that outlives that event. ## Current write behavior The current `@opencomputer/agent` API exposes session data as a read-only snapshot. It does not yet include a hook for setting or updating a value from agent code. Code should treat the returned value as immutable. See [Sessions and turns](/agents/sessions) for the durable conversation lifecycle and [Exa research session](/agents/examples/exa-research-session) for a starter that continues follow-up questions in one session. # Sessions and turns Source: https://docs.opencomputer.dev/agents/sessions Understand durable conversations and multi-turn agent work A session is a durable conversation with a deployed agent. Each turn appends input and streamed runtime events to the session, allowing clients to resume a conversation without rebuilding its history locally. ## React When you select the React SPA, the starter imports `useAgent()` from `@opencomputer/react`. The hook exposes: * `messages` — accumulated user and assistant messages * `send(text)` — create a session if necessary and stream the next turn * `isRunning` — whether a turn is active * `error` — the latest request error The hook accepts an agent selector such as `hello-world@development`. Build additional UI and application hooks around its session state as needed. ## Agent playground Open a project in the dashboard and choose **Agent playground**. Select the agent and environment independently, then create a new session or resume a previous playground session. Playground sessions stay in the playground list. Sessions started through an API appear under **Sessions**, where their durable turn history can be inspected. See [Playground and debugging](/agents/playground) for target selection and the debug inspector. ## CLI Start an interactive session against the development agent: ```bash theme={null} npm run session ``` Or manage remote sessions explicitly: ```bash theme={null} npx opencomputer session create "Draft a response" --remote \ --agent hello-world@development npx opencomputer session list npx opencomputer session inspect npx opencomputer session attach npx opencomputer session send "Continue" npx opencomputer session end ``` Use `--keep` on supported commands when the session should remain active after the command exits. ## Input sources Inside an agent, `useInput().source` identifies how the current work arrived. This lets the same agent distinguish direct work from delegated subagent work without defining another session type. # Skills Source: https://docs.opencomputer.dev/agents/skills Package reusable instructions and supporting resources with an agent A skill is a reusable instruction bundle that teaches an agent how to perform a task. A tool executes code; a skill provides a procedure, checklist, or domain playbook the agent can load when relevant. ## Create a skill Add a directory containing `SKILL.md` under the agent: ```text theme={null} opencomputer/agents/campaign-writer/ ├── agent.ts └── skills/ └── campaign-brief/ ├── SKILL.md └── CHECKLIST.md ``` Write a clear name and routing description in the frontmatter, then put the full procedure in the body: ```md theme={null} --- name: campaign-brief description: Create a sourced campaign brief. Use when planning a new campaign or revising its audience and messaging. --- # Campaign brief 1. Restate the product, audience, goal, and constraints. 2. Research the audience before proposing messaging. 3. Separate sourced facts from recommendations. 4. Produce a positioning statement, three message pillars, and five concepts. 5. Read `CHECKLIST.md` before returning the final brief. ``` The description should say both what the skill does and when it applies. The agent uses it to decide whether to load the instructions. ## Use the skill Skills do not need a hook. OpenComputer packages the entire `skills/` directory with that agent, which can load a matching skill during the session. Tell the agent when the skill should guide its work: ```tsx theme={null} export default function Agent() { return [ "Plan and improve marketing campaigns.", "For a new campaign brief, use the campaign-brief skill and follow its checklist.", ].join(" "); } ``` ## Add supporting files Keep the core procedure in `SKILL.md`. Put longer material beside it: ```text theme={null} campaign-brief/ ├── SKILL.md ├── CHECKLIST.md ├── VOICE.md └── examples/ └── launch-brief.md ``` Reference those files from `SKILL.md` so the agent knows when to read them. Supporting files are deployed with the skill and need no imports from `agent.ts`. ## Scope skills to an agent Skills live under `opencomputer/agents//skills/`, so each deployment receives only its own skill set. To reuse a skill, copy or generate the skill directory into each agent that needs it. ## Skills versus tools | Skill | Tool | | ------------------------------------- | --------------------------------- | | Markdown instructions and resources | Executable TypeScript | | Teaches a repeatable workflow | Performs a specific operation | | Loaded when its description matches | Called with JSON Schema arguments | | May tell the agent which tools to use | Produces a JSON-compatible result | Many useful workflows combine both: a research skill explains the process while an [Exa search tool](/agents/examples/exa-research-session) retrieves current sources. # Subagents Source: https://docs.opencomputer.dev/agents/subagents Let an agent delegate focused work with useSubagent A subagent is another agent in the same project that can take a focused task. Use `useSubagent()` to make that agent available to the current render. ## Add the specialist Create a second agent: ```tsx theme={null} // opencomputer/agents/researcher/agent.ts import { useModel, useTool } from "@opencomputer/agent"; export default function Agent() { useModel("anthropic/claude-sonnet-4.6"); useTool("web-search"); return "Research the delegated question. Return sourced findings only."; } ``` Register both agents in the project: ```tsx theme={null} // opencomputer/project.ts export default { name: "Campaign workspace", agents: ["campaign-planner", "researcher"], }; ``` ## Expose it with `useSubagent` ```tsx theme={null} // opencomputer/agents/campaign-planner/agent.ts import { useModel, useSubagent } from "@opencomputer/agent"; export default function Agent() { useModel("anthropic/claude-sonnet-4.6"); useSubagent("researcher"); return [ "Plan the campaign from evidence.", "Delegate audience and competitor research to the researcher.", ].join(" "); } ``` The value passed to `useSubagent()` is the agent ID from `opencomputer/project.ts`. Give the parent clear instructions about what it should delegate and what result it expects. Inputs created by delegation have `useInput().source === "subagent"`, so the specialist can adapt its response: ```tsx theme={null} import { useInput } from "@opencomputer/agent"; export default function Agent() { const input = useInput(); return input.source === "subagent" ? "Complete only the delegated research task and return concise findings." : "Help the user research their question."; } ``` ## Attach conditionally Like other resource hooks, `useSubagent()` can be conditional: ```tsx theme={null} const input = useInput(); if (input.text?.includes("research")) { useSubagent("researcher"); } ``` This keeps the delegate unavailable for requests where it adds no value. # Tools Source: https://docs.opencomputer.dev/agents/tools Define TypeScript tools and expose them with useTool A tool is TypeScript code the model may call while it works. Use tools for actions such as searching an API, looking up an order, or updating an external system. The model decides when a tool fits; your `run` function controls what happens. ## Define your first tool Create a tool beside the agent, for example `opencomputer/agents/support/tools/lookup-order.ts`: ```tsx theme={null} import { defineTool } from "@opencomputer/agent"; export const lookupOrder = defineTool({ name: "lookup_order", description: "Look up an order by ID and return its current status", input: { type: "object", properties: { orderId: { type: "string", description: "The customer order ID" }, }, required: ["orderId"], additionalProperties: false, }, async run({ input }) { const orderId = String(input.orderId); return { orderId, status: "processing" }; }, }); ``` Then attach it from `agent.ts`: ```tsx theme={null} import { useModel, useTool } from "@opencomputer/agent"; import { lookupOrder } from "./tools/lookup-order"; export default function Agent() { useModel("anthropic/claude-sonnet-4.6"); useTool(lookupOrder); return "Help customers check orders. Ask for an order ID before lookup."; } ``` The model sees the tool's name, description, and input schema. When it calls the tool, OpenComputer runs `run()` in the managed agent runtime and returns the result to the model. ## Tool definition | Field | Required | Purpose | | ------------- | -------- | ------------------------------------------------------------------- | | `name` | yes | ID the model calls; letters, numbers, underscores, and hyphens only | | `description` | yes | Explains what the tool does and when to use it | | `input` | no | JSON Schema for tool arguments | | `output` | no | JSON Schema describing the result | | `run` | yes | Synchronous or asynchronous implementation | Descriptions are the model's documentation. Include the action, when it is appropriate, and any important precondition. ## Execution context The `run` function receives: | Value | Purpose | | -------------------------- | ----------------------------------------------- | | `input` | Arguments created for the tool call | | `sessionId` | Current session ID | | `messageId` | Message containing the tool call | | `agentId` | Agent executing the call | | `signal` | Optional cancellation signal for abortable work | | `reportProgress(metadata)` | Emits JSON-compatible progress metadata | Use the cancellation signal with `fetch()` and report progress before slow steps: ```tsx theme={null} export const importCatalog = defineTool({ name: "import_catalog", description: "Import the latest product catalog; use only when asked", async run({ signal, reportProgress }) { await reportProgress({ phase: "downloading" }); const response = await fetch("https://example.com/catalog.json", { signal }); await reportProgress({ phase: "processing" }); return { imported: response.ok }; }, }); ``` Tool results and progress metadata must be JSON-compatible values. ## Conditional tools `useTool()` may be conditional. This keeps sensitive or specialized actions out of the tool set until they are relevant: ```tsx theme={null} import { useInput, useTool } from "@opencomputer/agent"; import { issueRefund } from "./tools/issue-refund"; export default function Agent() { const input = useInput(); if (input.text?.toLowerCase().includes("refund")) { useTool(issueRefund); } return "Resolve the support request. Confirm the order before a refund."; } ``` `useTool()` also accepts a tool ID such as `useTool("web-search")` when the runtime already provides that tool. ## Call external APIs safely Do not embed API keys in a tool or read them from the runtime environment. Declare an HTTP connection, store the value as an OpenComputer secret, and call the connection's `fetch()` method. See [Secrets and outbound requests](/agents/secrets) for a complete example. ## Tools versus skills and MCP * A tool executes TypeScript you own. * A [skill](/agents/skills) teaches the agent a reusable procedure. * An [MCP server](/agents/mcp) supplies a remote collection of tools. # Create Reservation Source: https://docs.opencomputer.dev/api-reference/capacity/create-reservations POST /api/capacity/reservations Commit capacity across one or more 15-minute UTC intervals in a single atomic write. The result is one reservation event with a server-generated `reservationId` covering all intervals in the request. See [Reserving capacity](/reserved-capacity/reserving) for the full write contract. Send an `Idempotency-Key` header so retries return the original result. Same key with a different body returns `idempotency_key_conflict`. Reservations are non-refundable — there is no cancellation, modification, or transfer. One entry per 15-minute interval to reserve. Interval start. RFC 3339, UTC, aligned to 15 minutes (`:00`, `:15`, `:30`, `:45`). Interval end. Must equal `startsAt + 15 minutes`. Multi-interval spans are rejected. Positive multiple of 4 (the grain is 1 GB-hour = 4 GB × 15 min). Other values are rejected. ```json 200 theme={null} { "reservationId": "9f67b8f7-7b91-4d2d-b1cb-19d0d0a14562", "createdAt": "2026-04-28T18:00:05Z", "intervals": [ { "startsAt": "2026-04-29T02:00:00Z", "endsAt": "2026-04-29T02:15:00Z", "capacityGb": 16 }, { "startsAt": "2026-04-29T02:15:00Z", "endsAt": "2026-04-29T02:30:00Z", "capacityGb": 16 } ] } ``` ```json 409 capacity_not_available theme={null} { "error": "capacity_not_available", "intervals": [ { "startsAt": "2026-04-29T02:00:00Z", "requestedGb": 80, "reservableGb": 28, "reason": "insufficient_capacity" } ] } ``` # Get Calendar Source: https://docs.opencomputer.dev/api-reference/capacity/get-calendar GET /api/capacity/calendar Return a planning snapshot of 15-minute UTC intervals in the requested window. One row per interval with current `reservedGb`, `reservableGb`, and the per-org `reservationLimitGb`. See [Reading the calendar](/reserved-capacity/calendar) for field semantics. Window start. RFC 3339, aligned to 15 minutes, UTC. Window end. RFC 3339, aligned to 15 minutes, UTC. ```json 200 theme={null} { "from": "2026-04-29T02:00:00Z", "to": "2026-04-29T04:00:00Z", "resource": "memory_gb", "intervals": [ { "startsAt": "2026-04-29T02:00:00Z", "endsAt": "2026-04-29T02:15:00Z", "reservationLimitGb": 300, "reservedGb": 80, "reservableGb": 220 } ] } ``` # List Reservations Source: https://docs.opencomputer.dev/api-reference/capacity/list-reservations GET /api/capacity/reservations Return reservation events made by the authenticated org, in reverse-chronological order by `createdAt`. Each reservation row includes all the intervals it committed to. Useful for end-of-month reconciliation and audit. Return reservations whose `createdAt >= from`. RFC 3339, UTC. Return reservations whose `createdAt < to`. RFC 3339, UTC. Pagination cursor from a previous response's `nextCursor`. Maximum reservations per page. Server-defined ceiling applies. ```json 200 theme={null} { "from": "2026-04-01T00:00:00Z", "to": "2026-05-01T00:00:00Z", "reservations": [ { "reservationId": "9f67b8f7-7b91-4d2d-b1cb-19d0d0a14562", "createdAt": "2026-04-28T18:00:05Z", "intervals": [ { "startsAt": "2026-04-29T02:00:00Z", "endsAt": "2026-04-29T02:15:00Z", "capacityGb": 16 }, { "startsAt": "2026-04-29T02:15:00Z", "endsAt": "2026-04-29T02:30:00Z", "capacityGb": 16 } ] } ], "nextCursor": null } ``` # Create Checkpoint Source: https://docs.opencomputer.dev/api-reference/checkpoints/create POST /api/sandboxes/{id}/checkpoints Create a checkpoint of the sandbox state. Each sandbox can have up to 10 full checkpoints and up to 100 disk-only checkpoints. By default, creating past the limit for that checkpoint type returns an error. Set `retentionPolicy.mode` to `delete_oldest` to delete the oldest eligible checkpoint of the same type first so the new checkpoint can be created. Sandbox ID Checkpoint name (unique per sandbox) Checkpoint type. Use `full` to preserve disk, memory, and CPU state, or `disk_only` to preserve only disk state with a larger per-sandbox limit. Optional retention policy. Use `{ "mode": "delete_oldest", "maxCount": 10 }` for full checkpoints or `{ "mode": "delete_oldest", "maxCount": 100 }` for disk-only checkpoints to delete the oldest eligible checkpoint of the same type before creating a new one. ```json 201 theme={null} { "id": "cp-abc123", "sandboxID": "sb-abc123", "name": "before-migration", "status": "processing", "sizeBytes": 0, "createdAt": "2025-01-15T10:30:00Z" } ``` # Delete Checkpoint Source: https://docs.opencomputer.dev/api-reference/checkpoints/delete DELETE /api/sandboxes/{id}/checkpoints/{checkpointId} Delete a checkpoint. Sandbox ID Checkpoint ID ```json 204 theme={null} {} ``` # Fork from Checkpoint Source: https://docs.opencomputer.dev/api-reference/checkpoints/fork POST /api/sandboxes/from-checkpoint/{checkpointId} Create a new sandbox from a checkpoint. Checkpoint ID Idle timeout for the new sandbox (default: `300`) Environment variables to override on the fork. Keys that match the checkpoint's stored envs are replaced; new keys are added. Name of a secret store to attach. If the checkpoint already has a store, secrets are merged — the new store's values win on collision and egress allowlists are aggregated. Memory for the forked sandbox, in MB. Clamped to a valid range: the floor is the checkpoint's own memory (a fork can't start smaller than the snapshot it restores, so a smaller value is ignored) and the ceiling is 16384 (16 GB; larger values are capped). The `memoryMB` field in the response reports the effective value after clamping. ```json 201 theme={null} { "sandboxID": "sb-def456", "status": "running", "region": "use2", "workerID": "w-use2-abc123", "memoryMB": 4096 } ``` # List Checkpoints Source: https://docs.opencomputer.dev/api-reference/checkpoints/list GET /api/sandboxes/{id}/checkpoints List all checkpoints for a sandbox. Sandbox ID ```json 200 theme={null} [ { "id": "cp-abc123", "sandboxID": "sb-abc123", "name": "before-migration", "status": "ready", "sizeBytes": 134217728, "createdAt": "2025-01-15T10:30:00Z" } ] ``` # Restore Checkpoint Source: https://docs.opencomputer.dev/api-reference/checkpoints/restore POST /api/sandboxes/{id}/checkpoints/{checkpointId}/restore Revert the sandbox in-place to a checkpoint. All changes since the checkpoint are lost. Sandbox ID Checkpoint ID ```json 200 theme={null} {} ``` # Create Exec Session Source: https://docs.opencomputer.dev/api-reference/exec/create-session POST /api/sandboxes/{id}/exec Start a long-running command as a session. Attach via WebSocket to stream I/O. Sandbox ID Command to execute Command arguments Environment variables Working directory Timeout in seconds Seconds to keep running after all clients disconnect ```json 201 theme={null} { "sessionID": "es-abc123", "sandboxID": "sb-abc123", "command": "node", "args": ["server.js"], "running": true, "exitCode": null, "startedAt": "2025-01-15T10:30:00Z", "attachedClients": 0 } ``` # Kill Exec Session Source: https://docs.opencomputer.dev/api-reference/exec/kill-session POST /api/sandboxes/{id}/exec/{sessionID}/kill Kill an exec session. Sandbox ID Exec session ID Signal number (default: `9` / SIGKILL) ```json 204 theme={null} {} ``` # List Exec Sessions Source: https://docs.opencomputer.dev/api-reference/exec/list-sessions GET /api/sandboxes/{id}/exec List all exec sessions for a sandbox. Sandbox ID ```json 200 theme={null} [ { "sessionID": "es-abc123", "sandboxID": "sb-abc123", "command": "node", "args": ["server.js"], "running": true, "exitCode": null, "startedAt": "2025-01-15T10:30:00Z", "attachedClients": 0 } ] ``` # Run Command Source: https://docs.opencomputer.dev/api-reference/exec/run POST /api/sandboxes/{id}/exec/run Execute a command synchronously and return the result. Sandbox ID Command to execute Command arguments Environment variables Working directory Timeout in seconds (default: `60`) ```json 200 theme={null} { "exitCode": 0, "stdout": "Hello, World!\n", "stderr": "" } ``` # Delete File or Directory Source: https://docs.opencomputer.dev/api-reference/files/delete DELETE /api/sandboxes/{id}/files Delete a file or directory. Sandbox ID Path to the file or directory to delete ```json 204 theme={null} {} ``` # Generate Download URL Source: https://docs.opencomputer.dev/api-reference/files/generate-download-url POST /api/sandboxes/{id}/files/download-url Generates a signed URL for downloading a file without an API key. See [Signed URLs](/sandboxes/signed-urls) for usage guide. Sandbox ID Absolute path to the file URL lifetime in seconds (max: 86400) ```json 200 theme={null} { "url": "https://app.opencomputer.dev/api/sandboxes/sb-xxx/files/download?path=%2Fapp%2Foutput.zip&expires=1773906434&signature=abc123", "expiresAt": "2025-01-01T01:00:00Z" } ``` # Generate Upload URL Source: https://docs.opencomputer.dev/api-reference/files/generate-upload-url POST /api/sandboxes/{id}/files/upload-url Generates a signed URL for uploading a file without an API key. See [Signed URLs](/sandboxes/signed-urls) for usage guide. Sandbox ID Absolute path for the destination file URL lifetime in seconds (max: 86400) ```json 200 theme={null} { "url": "https://app.opencomputer.dev/api/sandboxes/sb-xxx/files/upload?path=%2Fapp%2Finput.csv&expires=1773906434&signature=abc123", "expiresAt": "2025-01-01T01:00:00Z" } ``` # List Directory Source: https://docs.opencomputer.dev/api-reference/files/list-directory GET /api/sandboxes/{id}/files/list List contents of a directory. Sandbox ID Directory path (default: `/`) ```json 200 theme={null} [ { "name": "app", "isDir": true, "size": 4096, "path": "/app" }, { "name": "README.md", "isDir": false, "size": 1234, "path": "/README.md" } ] ``` # Create Directory Source: https://docs.opencomputer.dev/api-reference/files/mkdir POST /api/sandboxes/{id}/files/mkdir Create a new directory. Sandbox ID Path for the new directory ```json 204 theme={null} {} ``` # Read File Source: https://docs.opencomputer.dev/api-reference/files/read GET /api/sandboxes/{id}/files Returns file content as plain text (not JSON). Sandbox ID Absolute path to the file ```text 200 theme={null} Hello, World! ``` # Signed Download Source: https://docs.opencomputer.dev/api-reference/files/signed-download GET /api/sandboxes/{id}/files/download Download a file using a signed URL. No authentication required — the signature and expiry are validated from query parameters. Generate a signed URL first via [Generate Download URL](/api-reference/files/generate-download-url). Sandbox ID Absolute path to the file Unix timestamp when the URL expires HMAC-SHA256 signature ```text 200 theme={null} (file content with Content-Length header) ``` ```json 403 theme={null} { "error": "signed URL has expired" } ``` # Signed Upload Source: https://docs.opencomputer.dev/api-reference/files/signed-upload PUT /api/sandboxes/{id}/files/upload Upload a file using a signed URL. No authentication required — the signature and expiry are validated from query parameters. Send file content as the request body. Generate a signed URL first via [Generate Upload URL](/api-reference/files/generate-upload-url). Sandbox ID Absolute path for the destination file Unix timestamp when the URL expires HMAC-SHA256 signature ```text 204 theme={null} (no content) ``` ```json 403 theme={null} { "error": "invalid signature" } ``` # Write File Source: https://docs.opencomputer.dev/api-reference/files/write PUT /api/sandboxes/{id}/files Write content to a file. The request body is written as raw file content (not JSON). Sandbox ID Absolute path for the destination file ```json 204 theme={null} {} ``` # API Reference Source: https://docs.opencomputer.dev/api-reference/overview Complete REST API reference for OpenComputer ## Base URL ``` https://app.opencomputer.dev/api ``` ## Authentication All requests require an API key passed via the `X-API-Key` header. ```bash theme={null} curl https://app.opencomputer.dev/api/sandboxes \ -H "X-API-Key: $OPENCOMPUTER_API_KEY" ``` ## WebSocket Authentication WebSocket endpoints accept auth via query parameter: ``` wss://app.opencomputer.dev/api/sandboxes/{id}/exec/{sessionID}?api_key= ``` ## Error Format All errors use a consistent JSON envelope: ```json theme={null} { "error": "descriptive error message" } ``` | Status Code | Meaning | | ----------- | ---------------------------------------------- | | `400` | Invalid request (missing fields, bad values) | | `401` | Missing or invalid authentication | | `403` | Insufficient permissions | | `404` | Resource not found | | `409` | Conflict (duplicate resource) | | `429` | Quota exceeded | | `500` | Internal server error | | `503` | Feature unavailable in current deployment mode | # Create Patch Source: https://docs.opencomputer.dev/api-reference/patches/create POST /api/sandboxes/checkpoints/{checkpointId}/patches Create a patch script that runs when a sandbox is spawned from a checkpoint. Patches apply in sequence order. Checkpoint ID Bash script to execute on spawn Human-readable description ```json 201 theme={null} { "patch": { "id": "pa-abc123", "checkpointId": "cp-abc123", "script": "apt install -y curl", "description": "Install curl", "strategy": "on_wake", "sequence": 1, "createdAt": "2025-01-15T10:30:00Z" } } ``` # Delete Patch Source: https://docs.opencomputer.dev/api-reference/patches/delete DELETE /api/sandboxes/checkpoints/{checkpointId}/patches/{patchId} Delete a patch. Checkpoint ID Patch ID ```json 204 theme={null} {} ``` # List Patches Source: https://docs.opencomputer.dev/api-reference/patches/list GET /api/sandboxes/checkpoints/{checkpointId}/patches List all patches for a checkpoint. Checkpoint ID ```json 200 theme={null} [ { "id": "pa-abc123", "checkpointId": "cp-abc123", "script": "apt install -y curl", "description": "Install curl", "strategy": "on_wake", "sequence": 1, "createdAt": "2025-01-15T10:30:00Z" } ] ``` # Create Preview URL Source: https://docs.opencomputer.dev/api-reference/preview/create POST /api/sandboxes/{id}/preview Create a preview URL to expose a port from the sandbox. Sandbox ID Container port (1–65535) Custom domain Authentication configuration ```json 201 theme={null} { "id": "pv-abc123", "sandboxId": "sb-abc123", "hostname": "sb-abc123-p3000.workers.opencomputer.dev", "port": 3000, "sslStatus": "active", "createdAt": "2025-01-15T10:30:00Z" } ``` # Delete Preview URL Source: https://docs.opencomputer.dev/api-reference/preview/delete DELETE /api/sandboxes/{id}/preview/{port} Delete a preview URL. Sandbox ID Port number ```json 204 theme={null} {} ``` # List Preview URLs Source: https://docs.opencomputer.dev/api-reference/preview/list GET /api/sandboxes/{id}/preview List all preview URLs for a sandbox. Sandbox ID ```json 200 theme={null} [ { "id": "pv-abc123", "sandboxId": "sb-abc123", "hostname": "sb-abc123-p3000.workers.opencomputer.dev", "port": 3000, "sslStatus": "active", "createdAt": "2025-01-15T10:30:00Z" } ] ``` # Rotate Preview-URL Auth Token Source: https://docs.opencomputer.dev/api-reference/preview/rotate-auth POST /api/sandboxes/{id}/preview/rotate Mint a new bearer token for the sandbox's preview-URL auth gate. The old token stops working immediately — there is no zero-downtime dual-token mode, so roll the new token out to your caller before discarding the old one. If the sandbox was originally created without [`previewAuth`](/api-reference/sandboxes/create), this call installs a token and starts enforcing the gate from that point on. The plaintext is returned exactly once in the response. Only the SHA-256 hash is persisted server-side; the server cannot show you the token again later. Sandbox ID ```json 200 theme={null} { "previewAuthToken": "qx2sSi5IYXWBvnnRqwK9Ky_cIAI-x0Vx1bPCt0XMxsI", "scheme": "bearer" } ``` ### Errors | Status | When | | ------ | ----------------------------------------------------- | | `404` | Sandbox not found, or owned by a different org | | `410` | Sandbox is `stopped` or `error` | | `503` | Database not configured (combined-mode CP without PG) | # Create PTY Session Source: https://docs.opencomputer.dev/api-reference/pty/create POST /api/sandboxes/{id}/pty Create an interactive terminal session. Sandbox ID Terminal columns (default: `80`) Terminal rows (default: `24`) Shell path (default: `/bin/bash`) ```json 201 theme={null} { "sessionID": "ps-abc123", "sandboxID": "sb-abc123" } ``` # Kill PTY Session Source: https://docs.opencomputer.dev/api-reference/pty/kill DELETE /api/sandboxes/{id}/pty/{sessionID} Terminate a PTY session. Sandbox ID PTY session ID ```json 204 theme={null} {} ``` # Resize PTY Source: https://docs.opencomputer.dev/api-reference/pty/resize POST /api/sandboxes/{id}/pty/{sessionID}/resize Resize a PTY session. PTY resize is HTTP-only — not exposed in the TypeScript or Python SDKs. The SDKs handle resize automatically for interactive sessions. Sandbox ID PTY session ID New column count New row count ```json 200 theme={null} {} ``` # Create Sandbox Source: https://docs.opencomputer.dev/api-reference/sandboxes/create POST /api/sandboxes Create a new sandbox. Template name (default: `"base"`) Idle timeout in seconds (default: `300`) CPU cores. If omitted but `memoryMB` is set, inferred automatically. Memory in MB. If omitted but `cpuCount` is set, inferred automatically. Create a [Burst Sandbox](/sandboxes/burst-sandboxes). Disk is preserved across infrastructure restarts; processes may restart. The 1 GB tier provides 1 vCPU on a best-effort basis. For guaranteed CPU allocation, use the 4 GB tier or above. If both `cpuCount` and `memoryMB` are provided, they must match a platform tier. Environment variables as key-value pairs Arbitrary key-value pairs Declarative image manifest (see [Image builder](/sandboxes/templates#image-configuration)) Name of a pre-built snapshot for instant boot Register [webhook](/sandboxes/webhooks) destination(s) for this sandbox's lifecycle events, pinned to this sandbox. Registering inline (rather than via a separate call) means the endpoints exist **before** `sandbox.created` / `sandbox.ready` are relayed, so you don't miss the first events. * `url` *(string, required)*: HTTPS endpoint. * `secret` *(string)*: signing secret; omit and one is generated and returned on the response (`webhooks[].secret`), also re-fetchable later. * `eventTypes` *(string\[])*: event-type allow-list (default all). Each spec is validated up-front (same rules as [`POST /api/webhooks`](/api-reference/webhooks/create)): a non-HTTPS `url` or unknown `eventTypes` is rejected with `400`. Registration is otherwise **best-effort** — if a spec fails to register downstream, the sandbox is still created; inspect the echoed `webhooks: [{ id, url, secret? }]` to see what registered. Opt in to bearer-token authentication on the sandbox's preview URLs. When set, every request to `https://sb--p.` must include an `Authorization: Bearer ` (or `X-OC-Preview-Token: `) header; missing or wrong → 401. * `scheme` *(string)*: must be `"bearer"`. Reserved for HMAC/JWT later. * `token` *(string)*: `"auto"` (or omitted) → server generates a 256-bit random token. An explicit string of at least 16 characters lets you bring your own. The plaintext is returned exactly once in the response as `previewAuthToken`; only its SHA-256 hash is stored. Use [`POST /api/sandboxes/{id}/preview/rotate`](/api-reference/preview/rotate-auth) to mint a new one. Omit this field for the legacy open behavior — preview URLs respond to anyone who can reach the hostname. ```json 201 theme={null} { "sandboxID": "sb-abc123", "status": "running", "region": "use2", "workerID": "w-use2-abc123", "previewAuthToken": "qx2sSi5IYXWBvnnRqwK9Ky_cIAI-x0Vx1bPCt0XMxsI", "webhooks": [ { "id": "whk_3f9a2c", "url": "https://app.example.com/oc-webhook", "secret": "whsec_Hk9…" } ] } ``` `previewAuthToken` is only present when `previewAuth` was set in the request — read it once and store it durably; the server will not return it again. `webhooks` is present only when `webhooks` were requested; each `secret` (`whsec_…`) is returned here and stays re-fetchable via [`GET /api/webhooks/{id}/secret`](/api-reference/webhooks/get). # Kill Sandbox Source: https://docs.opencomputer.dev/api-reference/sandboxes/delete DELETE /api/sandboxes/{id} Terminate and remove a sandbox. Sandbox ID ```json 204 theme={null} {} ``` # Get Sandbox Source: https://docs.opencomputer.dev/api-reference/sandboxes/get GET /api/sandboxes/{id} Get sandbox details by ID. Available on both control plane and worker. Sandbox ID ```json 200 theme={null} { "sandboxID": "sb-abc123", "status": "running", "region": "use2", "workerID": "w-use2-abc123", "tags": { "env": "prod", "team": "payments" }, "tagsLastUpdatedAt": "2026-04-19T14:02:00Z" } ``` `tags` and `tagsLastUpdatedAt` are always present — empty object and `null` respectively when no tags are set. Update them with [`PUT /api/sandboxes/{id}/tags`](/api-reference/sandboxes/set-tags). # Get Sandbox Tags Source: https://docs.opencomputer.dev/api-reference/sandboxes/get-tags GET /api/sandboxes/{id}/tags Read the current tag set for a sandbox. **Preview:** Usage and tag APIs are new. Endpoints, response fields, and SDK method names may change before GA; temporary inaccuracies or rough edges are possible while the surface settles. Sandbox ID. ```json 200 theme={null} { "tags": { "env": "prod", "team": "payments" }, "tagsLastUpdatedAt": "2026-04-19T14:02:00Z" } ``` A sandbox with no tags returns `"tags": {}` and `"tagsLastUpdatedAt": null`. # Hibernate Sandbox Source: https://docs.opencomputer.dev/api-reference/sandboxes/hibernate POST /api/sandboxes/{id}/hibernate Snapshot VM state and stop the sandbox. Control plane only. Sandbox ID ```json 200 theme={null} { "sandboxID": "sb-abc123", "hibernationKey": "checkpoints/sb-abc123/1234567890.tar.zst", "sizeBytes": 134217728, "status": "hibernated" } ``` # List Sandboxes Source: https://docs.opencomputer.dev/api-reference/sandboxes/list GET /api/sandboxes List all running sandboxes. ```json 200 theme={null} [ { "sandboxID": "sb-abc123", "status": "running", "region": "use2", "workerID": "w-use2-abc123", "tags": { "env": "prod" }, "tagsLastUpdatedAt": "2026-04-19T14:02:00Z" } ] ``` Each entry carries `tags` (always present, empty object when unset) and `tagsLastUpdatedAt` (null when unset). To filter by tag, use [`GET /api/usage?groupBy=sandbox&filter[tag:]=`](/api-reference/usage/get-usage). # Set Sandbox Tags Source: https://docs.opencomputer.dev/api-reference/sandboxes/set-tags PUT /api/sandboxes/{id}/tags Replace the full tag set for a sandbox. Partial updates are not supported — GET, modify, PUT. `{}` clears all tags. **Preview:** Usage and tag APIs are new. Endpoints, response fields, and SDK method names may change before GA; temporary inaccuracies or rough edges are possible while the surface settles. Sandbox ID. Each top-level key in the body is a tag name; the value is a string. Flat map only — nested objects are rejected. ### Validation * At most 50 tag keys per sandbox. * Keys: 1–128 characters, `A–Z a–z 0–9 _ . - :`. `:` is allowed as a user namespace separator. * Values: 0–256 UTF-8 characters. * The `oc:` key prefix is reserved for future system-set tags. ```json 200 theme={null} { "tags": { "env": "staging", "team": "growth" }, "tagsLastUpdatedAt": "2026-04-22T20:31:14Z" } ``` Retagging rewrites attribution going forward: a sandbox re-tagged from `team=payments` to `team=growth` will appear under `growth` in every subsequent /usage query, including for time before the retag. Surface `tagsLastUpdatedAt` alongside usage in dashboards so readers can see when tags changed. # Set Timeout Source: https://docs.opencomputer.dev/api-reference/sandboxes/set-timeout POST /api/sandboxes/{id}/timeout Update the idle timeout for a sandbox. Sandbox ID New timeout in seconds (must be > 0) ```json 204 theme={null} {} ``` # Wake Sandbox Source: https://docs.opencomputer.dev/api-reference/sandboxes/wake POST /api/sandboxes/{id}/wake Resume a hibernated sandbox. Control plane only. Sandbox ID Idle timeout after wake (default: `300`) ```json 200 theme={null} { "sandboxID": "sb-abc123", "status": "running", "region": "use2", "workerID": "w-use2-abc123" } ``` # Create Snapshot Source: https://docs.opencomputer.dev/api-reference/snapshots/create POST /api/snapshots Create a pre-built sandbox environment from a declarative image manifest. Unique snapshot name Declarative image manifest (see [Image builder](/sandboxes/templates#image-configuration)) ```json 201 theme={null} { "id": "snap-abc123", "name": "data-science", "status": "building", "contentHash": "sha256:...", "checkpointId": "", "manifest": { "steps": [] }, "createdAt": "2025-01-15T10:30:00Z", "lastUsedAt": "" } ``` # Delete Snapshot Source: https://docs.opencomputer.dev/api-reference/snapshots/delete DELETE /api/snapshots/{name} Delete a snapshot. Snapshot name ```json 204 theme={null} {} ``` # Get Snapshot Source: https://docs.opencomputer.dev/api-reference/snapshots/get GET /api/snapshots/{name} Get snapshot details by name. Snapshot name ```json 200 theme={null} { "id": "snap-abc123", "name": "data-science", "status": "ready", "contentHash": "sha256:...", "checkpointId": "cp-abc123", "manifest": { "steps": [] }, "createdAt": "2025-01-15T10:30:00Z", "lastUsedAt": "2025-01-16T08:00:00Z" } ``` # List Snapshots Source: https://docs.opencomputer.dev/api-reference/snapshots/list GET /api/snapshots List all snapshots. ```json 200 theme={null} [ { "id": "snap-abc123", "name": "data-science", "status": "ready", "contentHash": "sha256:...", "checkpointId": "cp-abc123", "manifest": { "steps": [] }, "createdAt": "2025-01-15T10:30:00Z", "lastUsedAt": "2025-01-16T08:00:00Z" } ] ``` # Sandbox Usage Source: https://docs.opencomputer.dev/api-reference/usage/get-sandbox-usage GET /api/sandboxes/{id}/usage Per-sandbox memory usage over a window, as 1-minute points plus envelope totals. Allocated memory is the tier the sandbox was provisioned at, integrated over time; used memory is the actual resident memory consumed by the sandbox process, sampled every 60s. **Preview:** Usage and tag APIs are new. Endpoints, response fields, and SDK method names may change before GA; temporary inaccuracies or rough edges are possible while the surface settles. ## Request Sandbox ID. Lower bound. Accepts either an ISO date (`2026-05-27`, interpreted as UTC midnight) or an RFC3339 timestamp. Default: now minus 1 hour. Upper bound. Same accepted formats as `from`. Default: now. Window must be ≤ 30 days. ## Response Echo of the requested sandbox ID. The sandbox's display name (set at create time). Absent if no alias was provided. RFC3339 lower bound of the window the response covers — either the caller's `from`, or the server default (`now - 1h`) when omitted. RFC3339 upper bound. Either the caller's `to`, or the server default (`now`) when omitted. Envelope totals for the full window. Each additive field equals the sum of the matching field across `points[]` — useful for getting the headline numbers without iterating. Total GiB-seconds the sandbox was provisioned for. This is the physical quantity the bill is computed from. For a sandbox at 1 GiB running for 1 hour, this is `3600`. Total GiB-seconds the sandbox actually consumed (integrated resident memory). Compare against `memoryAllocatedGbSeconds` to see headroom — `1 - used/allocated` is wasted provisioning. Number of seconds the sandbox was provisioned (i.e., had an open scale event) within the window. A 1-hour window where the sandbox ran the whole time returns `3600`. Maximum provisioned memory tier (MiB) seen in the window. Reflects the highest size the sandbox was resized to. Maximum measured resident memory (MiB) in any sample during the window. Useful for detecting workloads that briefly approached their tier ceiling. Time-ordered array of 1-minute buckets covering `[from, to)`. Buckets are minute-aligned in UTC. Boundary buckets are clamped to the original window — a mid-minute `from` produces a partial first bucket whose integrals reflect only the overlap with the requested range. Minutes where the sandbox was not provisioned (before first start, after final stop) appear with all fields at `0` rather than as missing entries — gaps read as continuous flat zero on a chart. RFC3339 timestamp of the bucket start (minute-aligned, UTC). The bucket covers `[ts, ts + 1 minute)`. Provisioned memory integrated over this minute, in GiB-seconds. Summing this across all points reproduces `totals.memoryAllocatedGbSeconds` exactly. Measured resident memory integrated over this minute, in GiB-seconds. Same compositional property — summing reproduces `totals.memoryUsedGbSeconds`. Seconds within this minute the sandbox was provisioned. Usually `60`; `0` when not running; a fractional value (e.g., `30`) appears in the resize or start/stop bucket where the sandbox began or ended part-way through the minute. Time-weighted average of the provisioned memory tier (MiB) across this bucket. When a resize falls inside the bucket, this shows the blend (e.g., 30s at 2048 + 30s at 4096 → `3072`). Designed to render directly as a step line on a chart. Average measured resident memory (MiB) across samples that fell in this bucket. The natural Y value for a "memory used" chart. Maximum measured resident memory (MiB) across samples in this bucket. Catches sub-minute spikes that the average smooths out. ### Units All `*GbSeconds` fields are **GiB-seconds** (binary, 230 bytes/GiB), not decimal GB. For a sandbox provisioned at 1024 MiB running for one minute, `memoryAllocatedGbSeconds` is `1024/1024 × 60 = 60`. For a measured RSS of 612 MiB, `memoryUsedGbSeconds` is `612/1024 × 60 ≈ 35.86`. ```json 200 theme={null} { "sandboxId": "sb-abc", "alias": "my-agent", "from": "2026-05-27T00:00:00Z", "to": "2026-05-28T00:00:00Z", "totals": { "memoryAllocatedGbSeconds": 86400, "memoryUsedGbSeconds": 51640, "uptimeSeconds": 86400, "memoryAllocatedPeakMb": 1024, "memoryUsedPeakMb": 742 }, "points": [ { "ts": "2026-05-27T00:00:00Z", "memoryAllocatedGbSeconds": 60.0, "memoryUsedGbSeconds": 35.86, "uptimeSeconds": 60, "allocatedMemoryMb": 1024, "usedMemoryMbAvg": 612, "usedMemoryMbPeak": 720 }, { "ts": "2026-05-27T00:01:00Z", "memoryAllocatedGbSeconds": 60.0, "memoryUsedGbSeconds": 40.02, "uptimeSeconds": 60, "allocatedMemoryMb": 1024, "usedMemoryMbAvg": 683, "usedMemoryMbPeak": 742 } ] } ``` ### Use the aggregator for cross-sandbox questions To rank sandboxes by usage, use [`GET /api/usage`](/api-reference/usage/get-usage) — that endpoint serves cross-sandbox aggregates and supports filter/sort/cursor. The per-sandbox endpoint here is for "look at this one sandbox in detail." # Usage Aggregator Source: https://docs.opencomputer.dev/api-reference/usage/get-usage GET /api/usage Aggregate usage grouped by sandbox or by tag. Dollars are not exposed — the platform returns the same physical quantities the invoice is computed from. For per-sandbox time-series drilldown (memory utilization over time, 1-minute resolution), see [`GET /api/sandboxes/:id/usage`](/api-reference/usage/get-sandbox-usage). **Preview:** Usage and tag APIs are new. Endpoints, response fields, and SDK method names may change before GA; temporary inaccuracies or rough edges are possible while the surface settles. All `*GbSeconds` fields are **GiB-seconds** (binary, 230 bytes/GiB), not decimal GB. For a sandbox provisioned at 1024 MiB running for one second, `memoryGbSeconds` is `1`. Freshness: accurate to the minute under normal conditions. A sandbox that exited uncleanly continues to accrue against its provisioned tier until the platform reconciles state — the same behavior the billing pipeline uses, so the numbers here match the invoice. ## Request `sandbox` for top sandboxes, or `tag:` for usage by tag value. Keys may contain `:` — everything after the first `:` is the tag key (so `groupBy=tag:team:payments` groups by the key `team:payments`). Inclusive lower bound. Accepts either an ISO date (`2026-05-27`, interpreted as UTC midnight) or an RFC3339 timestamp. Default: now minus 30 days. Max window: 90 days. Exclusive upper bound. Same accepted formats as `from`. Default: now. One param per dimension. Comma-separated values are OR'd within that dimension; different `filter[...]` params are AND'd across dimensions. Passing the same `filter[...]` key twice returns 400 — put multiple OR values in one comma-separated string instead. Empty value (`filter[tag:team]=`) matches sandboxes without the `team` key. `-memoryGbSeconds` (default) or `-diskOverageGbSeconds`. Max items per page. Default 50, max 500. Opaque cursor from a prior response's `nextCursor`. ## Response RFC3339 echo of the effective window lower bound. RFC3339 echo of the effective window upper bound. Echo of the requested `groupBy` value. Aggregate across **all rows matching the window and filters**, not just the current page. Use this for headline numbers; sum `items[]` if you need a per-page subtotal instead. Total provisioned memory × time across matching sandboxes, in GiB-seconds. Total disk-overage × time. Disk usage within the free allowance does not contribute; this is the billable excess. Paginated rows, sorted by `sort` (default: largest first). When `groupBy=sandbox`, each row is one sandbox: The sandbox identifier. Display name set at create time. Absent when none was provided. Current sandbox status (e.g., `running`, `hibernated`, `stopped`). Absent when no session exists. Current tag set on the sandbox. Empty object when none. RFC3339 timestamp of the most recent tag change, or `null` if no tags exist. Provisioned memory × time for this sandbox in the window. Disk overage × time for this sandbox. When `groupBy=tag:`, each row is one tag value: The tag key from the request (e.g., `team`). The tag value for this group. Total provisioned memory × time across sandboxes carrying `tagKey=tagValue`. Total disk overage × time for the same group. Distinct sandboxes contributing to this row. Only present when `groupBy=tag:`. Sibling bucket for sandboxes that lack the grouping key — without it, untagged sandboxes would silently disappear from the rollup. Same shape as a tag row minus `tagKey`/`tagValue`. Total provisioned memory × time across untagged sandboxes. Total disk overage × time. Distinct sandboxes in the untagged bucket. Opaque cursor to pass as `cursor=` on the next request, or `null` when no further pages exist. ```json groupBy=sandbox theme={null} { "from": "2026-03-23T00:00:00Z", "to": "2026-04-22T00:00:00Z", "groupBy": "sandbox", "total": { "memoryGbSeconds": 20000, "diskOverageGbSeconds": 360 }, "items": [ { "sandboxId": "sb-abc", "alias": "my-agent", "status": "running", "tags": { "env": "prod", "team": "payments" }, "tagsLastUpdatedAt": "2026-04-19T14:02:00Z", "memoryGbSeconds": 8000, "diskOverageGbSeconds": 120 } ], "nextCursor": null } ``` ```json groupBy=tag:team theme={null} { "from": "2026-03-23T00:00:00Z", "to": "2026-04-22T00:00:00Z", "groupBy": "tag:team", "total": { "memoryGbSeconds": 19000, "diskOverageGbSeconds": 340 }, "untagged": { "memoryGbSeconds": 1000, "diskOverageGbSeconds": 20, "sandboxCount": 2 }, "items": [ { "tagKey": "team", "tagValue": "payments", "memoryGbSeconds": 8000, "diskOverageGbSeconds": 120, "sandboxCount": 12 }, { "tagKey": "team", "tagValue": "growth", "memoryGbSeconds": 4000, "diskOverageGbSeconds": 50, "sandboxCount": 5 } ], "nextCursor": null } ``` # List Tag Keys Source: https://docs.opencomputer.dev/api-reference/usage/list-tags GET /api/tags List all tag keys set on any sandbox in the org, with per-key counts of tagged sandboxes and distinct values. Useful for building group-by pickers without querying the full tag set. **Preview:** Usage and tag APIs are new. Endpoints, response fields, and SDK method names may change before GA; temporary inaccuracies or rough edges are possible while the surface settles. ```json 200 theme={null} { "keys": [ { "key": "team", "sandboxCount": 17, "valueCount": 4 }, { "key": "env", "sandboxCount": 23, "valueCount": 3 } ] } ``` Tag keys on torn-down sandboxes are retained so historical drilldowns keep resolving. As a result `sandboxCount` may include sandboxes that are no longer active. # Create Webhook Source: https://docs.opencomputer.dev/api-reference/webhooks/create POST /api/webhooks Register a webhook destination for sandbox lifecycle events. See [Webhooks](/sandboxes/webhooks). HTTPS endpoint to deliver to. (SSRF protection is applied by the delivery provider at send time, not at registration.) Event-type allow-list — exact (`sandbox.stopped`) or prefix (`sandbox.*`). Default: all event types. Types outside the sandbox taxonomy are rejected with `400`. Scope to a single sandbox. Omit to receive events for all of the org's sandboxes. Signing secret. Omit and one is generated (`whsec_…`). The secret is returned in the create response and is re-fetchable any time via [`GET /api/webhooks/{id}/secret`](/api-reference/webhooks/get). Optional display name for the destination. Whether the destination is active. Default `true`. `false` pauses delivery. Optional. A retried create with the same key **and same body** returns the **same** destination (`200`) instead of a duplicate. Reusing the key with a **different** body is a `409` conflict. Without an `Idempotency-Key`, each call creates a **new** destination and returns **`201`** — there is no get-or-create by `name`. With one, a retried call returns the same destination with **`200`**. **Validation (all `400`):** `url` must be HTTPS; each `eventTypes` entry must be a known sandbox event type or a `prefix.*` wildcard. ```json 201 theme={null} { "id": "whk_3f9a2c", "name": "prod", "url": "https://app.example.com/oc-webhook", "eventTypes": ["sandbox.stopped"], "sandboxId": null, "enabled": true, "hasSecret": true, "secret": "whsec_Hk9…", "createdAt": "2026-06-24T12:00:00Z", "updatedAt": "2026-06-24T12:00:00Z" } ``` The `secret` is returned here and stays re-fetchable via [`GET /api/webhooks/{id}/secret`](/api-reference/webhooks/get) — store it to verify deliveries. # Delete Webhook Source: https://docs.opencomputer.dev/api-reference/webhooks/delete DELETE /api/webhooks/{id} Delete a destination. It stops receiving events, drops out of [list](/api-reference/webhooks/list), and its delivery-provider endpoint is removed. Delivery history for a deleted destination is no longer queryable. See [Webhooks](/sandboxes/webhooks). Destination ID (`whk_…`). ```json 204 theme={null} ``` # List Deliveries Source: https://docs.opencomputer.dev/api-reference/webhooks/deliveries-list GET /api/webhooks/{id}/deliveries List a destination's most recent delivery attempts (up to 50). Each entry is a delivery-provider attempt record. The attempt index lags actual delivery by a few seconds, and resolving a single message ([get](/api-reference/webhooks/delivery-get)) / [redeliver](/api-reference/webhooks/redeliver) can lag longer (\~30s+) — retry with backoff. See [Webhooks](/sandboxes/webhooks#deliveries). Destination ID (`whk_…`). The message id — the key for [get](/api-reference/webhooks/delivery-get) / [redeliver](/api-reference/webhooks/redeliver); equals the `svix-id` header on the delivery. The id of this specific attempt. One of `success`, `pending`, or `failed`. The consumer's HTTP response code, when the attempt reached it. ```json 200 theme={null} { "data": [ { "id": "msg_2aB…", "attemptId": "atmpt_9fE…", "status": "success", "responseStatusCode": 200, "timestamp": "2026-06-24T12:00:01Z" } ] } ``` # Get Delivery Source: https://docs.opencomputer.dev/api-reference/webhooks/delivery-get GET /api/webhooks/{id}/deliveries/{deliveryId} Fetch one delivered message by id. See [Webhooks](/sandboxes/webhooks#deliveries). The delivery provider's attempt index and message store are eventually consistent: a message id can appear in [list deliveries](/api-reference/webhooks/deliveries-list) **before** this endpoint (and [redeliver](/api-reference/webhooks/redeliver)) can resolve it — observed up to \~30s+ after the attempt. So a `404` shortly after a delivery is transient — **retry with backoff** rather than treating it as final. Destination ID (`whk_…`). Message id (`msg_…`, the `id` from [list deliveries](/api-reference/webhooks/deliveries-list); equals the `svix-id` header). ```json 200 theme={null} { "id": "msg_2aB…", "eventType": "sandbox.stopped", "eventId": "sb-3f9a…:sandbox.stopped", "payload": { "type": "sandbox.stopped", "sandboxId": "sb-3f9a…", "eventId": "sb-3f9a…:sandbox.stopped", "event": { "id": "sb-3f9a…:sandbox.stopped", "ts": "2026-06-24T12:00:00Z", "orgId": "org_…", "sandboxId": "sb-3f9a…", "type": "sandbox.stopped", "data": { "reason": "user_requested" } } }, "timestamp": "2026-06-24T12:00:00Z" } ``` # Get Webhook Source: https://docs.opencomputer.dev/api-reference/webhooks/get GET /api/webhooks/{id} Fetch one webhook destination. The response carries `hasSecret` (not the secret itself); fetch the secret value any time via `GET /api/webhooks/{id}/secret` → `{ "secret": "whsec_…" }`. See [Webhooks](/sandboxes/webhooks). Destination ID (`whk_…`). ```json 200 theme={null} { "id": "whk_3f9a2c", "name": "prod", "url": "https://app.example.com/oc-webhook", "eventTypes": ["sandbox.stopped"], "sandboxId": null, "enabled": true, "hasSecret": true, "createdAt": "2026-06-24T12:00:00Z", "updatedAt": "2026-06-24T12:00:00Z" } ``` # List Webhooks Source: https://docs.opencomputer.dev/api-reference/webhooks/list GET /api/webhooks List the org's webhook destinations. Deleted destinations are excluded. See [Webhooks](/sandboxes/webhooks). ```json 200 theme={null} { "data": [ { "id": "whk_3f9a2c", "name": "prod", "url": "https://app.example.com/oc-webhook", "eventTypes": ["sandbox.stopped"], "sandboxId": null, "enabled": true, "hasSecret": true, "createdAt": "2026-06-24T12:00:00Z", "updatedAt": "2026-06-24T12:00:00Z" } ] } ``` # Redeliver Source: https://docs.opencomputer.dev/api-reference/webhooks/redeliver POST /api/webhooks/{id}/deliveries/{deliveryId}/redeliver Re-send a message to the endpoint. The redelivery carries the **same** `svix-id`, so a receiver that dedupes treats it as the same message. Use it when the original never landed. See [Webhooks](/sandboxes/webhooks#deliveries). Destination ID (`whk_…`). Message id (`msg_…`, from [list deliveries](/api-reference/webhooks/deliveries-list)). ```json 200 theme={null} { "ok": true } ``` The new attempt appears under [deliveries](/api-reference/webhooks/deliveries-list). # Test Webhook Source: https://docs.opencomputer.dev/api-reference/webhooks/test POST /api/webhooks/{id}/test Enqueue a sample event to exercise your endpoint. It sends a real, signed message of a concrete type the destination is subscribed to (the first of its `eventTypes`, defaulting to `sandbox.created`), carrying the **normal delivery envelope** with `event.data.test = true` — so you exercise your real verifier and parser. Delivery is **asynchronous** — this returns once the message is accepted; check [deliveries](/api-reference/webhooks/deliveries-list) for the outcome. The sample is published through your org's webhook app, so it is **not** isolated to this one destination: any other destination subscribed to the same event type (and matching scope) also receives it (likewise marked `event.data.test = true`). See [Webhooks](/sandboxes/webhooks). Destination ID (`whk_…`). ```json 200 theme={null} { "ok": true, "eventType": "sandbox.created", "messageId": "msg_2aB…" } ``` `messageId` is the delivery's message id — find its attempt under [deliveries](/api-reference/webhooks/deliveries-list). # Update Webhook Source: https://docs.opencomputer.dev/api-reference/webhooks/update PATCH /api/webhooks/{id} Update a destination — pause/resume, retune filters, change the URL, or rotate the secret. See [Webhooks](/sandboxes/webhooks). Destination ID (`whk_…`). New HTTPS endpoint. Replace the event-type allow-list (unknown types are rejected with `400`). Pass `null` to clear it (deliver all types). Pause (`false`) or resume (`true`) delivery. Pausing does **not** drop events — events that occur while paused are queued and delivered when you re-enable. Rotate to a **new** generated signing secret, returned as `secret` in the response. The previous secret stays valid for a short rollover window so in-flight deliveries still verify. (To set a specific secret, create a new destination.) Rename the destination. `sandboxId` (scope) is **immutable** — set it at create; it can't be changed here. The current signing secret is re-fetchable any time via [`GET /api/webhooks/{id}/secret`](/api-reference/webhooks/get). ```json 200 theme={null} { "id": "whk_3f9a2c", "name": "prod", "url": "https://app.example.com/oc-webhook", "eventTypes": ["sandbox.stopped", "sandbox.ready"], "sandboxId": null, "enabled": true, "hasSecret": true, "createdAt": "2026-06-24T12:00:00Z", "updatedAt": "2026-06-24T12:05:00Z" } ``` # Browser Sessions Source: https://docs.opencomputer.dev/browser-sessions/overview Preview: create cloud browser sessions for Playwright, Magnitude, and profile-backed web automation Browser Sessions are currently in invite-only preview. Access is enabled for approved organizations, and API routes, SDK method names, response fields, limits, and pricing may change before general availability. Browser Sessions create managed Chromium sessions for web agents and browser automation. OpenComputer handles API-key auth and org ownership, then returns browser connection URLs for tools such as Playwright and Magnitude. If your organization has not been invited to the preview, Browser Session API calls may fail even when your OpenComputer API key is valid. Use Browser Sessions when you want a cloud browser without installing Chromium inside an OpenComputer sandbox. Use [sandboxes](/sandboxes/overview) when you need a full Linux VM with your own browser runtime and filesystem. ## Create a Browser ```typescript TypeScript theme={null} import { Browser } from "@opencomputer/sdk"; const browser = await Browser.create({ headless: false, stealth: true, startUrl: "https://example.com", timeoutSeconds: 120, }); console.log(browser.id); console.log(browser.cdpWsUrl); console.log(browser.liveViewUrl); await browser.delete(); ``` ```python Python theme={null} from opencomputer import Browser browser = await Browser.create( headless=False, stealth=True, start_url="https://example.com", timeout_seconds=120, ) print(browser.id) print(browser.cdp_ws_url) print(browser.live_view_url) await browser.delete() ``` Headful browsers return a live-view URL. Headless browsers are lighter, but do not provide a live view. Telemetry is enabled by default for all browser sessions. Set `telemetry: false` / `telemetry=False` to disable it, or pass a Kernel telemetry configuration object directly. Replay recording is disabled by default. Set `recording: true` / `recording=True` to enable replay recording for a headful browser session. ## Profile Auth Checks Saved profiles can start an asynchronous auth check for a site. The SDK returns a run immediately; `wait()` polls with short requests until the run completes. ```typescript TypeScript theme={null} const profile = await BrowserProfile.connect("linkedin-profile"); const run = await profile.checkAuth({ homepage: "https://www.linkedin.com/feed/", user: "motatoes", mode: "vision", compareFresh: true, }); const result = await run.wait(); console.log(result.status, result.result); ``` Auth checks are read-only. They create temporary browser sessions with profile saving disabled and compare the saved profile against a fresh browser when `compareFresh` is enabled. ## Playwright The browser response includes a CDP WebSocket URL. Pass it directly to Playwright's `connectOverCDP`. ```typescript TypeScript theme={null} import { Browser } from "@opencomputer/sdk"; import { chromium } from "playwright"; const ocBrowser = await Browser.create({ headless: false, startUrl: "https://example.com", }); try { const browser = await chromium.connectOverCDP(ocBrowser.cdpWsUrl); const context = browser.contexts()[0] || await browser.newContext(); const page = context.pages()[0] || await context.newPage(); await page.goto("https://example.com"); console.log(await page.title()); await browser.close(); } finally { await ocBrowser.delete(); } ``` ```python Python theme={null} from opencomputer import Browser from playwright.async_api import async_playwright oc_browser = await Browser.create( headless=False, start_url="https://example.com", ) try: async with async_playwright() as p: browser = await p.chromium.connect_over_cdp(oc_browser.cdp_ws_url) context = browser.contexts[0] if browser.contexts else await browser.new_context() page = context.pages[0] if context.pages else await context.new_page() await page.goto("https://example.com") print(await page.title()) await browser.close() finally: await oc_browser.delete() ``` ## Magnitude Magnitude can use a Playwright-connected browser. Create the OpenComputer browser first, connect over CDP, then hand the page to your Magnitude agent. ```typescript TypeScript theme={null} import { Browser } from "@opencomputer/sdk"; import { chromium } from "playwright"; import { startBrowserAgent } from "magnitude-core"; const ocBrowser = await Browser.create({ headless: false, stealth: true, startUrl: "https://example.com", }); try { const browser = await chromium.connectOverCDP(ocBrowser.cdpWsUrl); const context = browser.contexts()[0] || await browser.newContext(); const page = context.pages()[0] || await context.newPage(); const agent = await startBrowserAgent({ page }); await agent.act("Find the page title and summarize what this site is for."); await browser.close(); } finally { await ocBrowser.delete(); } ``` Magnitude package names and constructors can vary by version. The OpenComputer-specific step is stable: create a `Browser`, connect to `cdpWsUrl` with Playwright, then pass the resulting page or browser object to Magnitude. ## Save and Load Profiles Profiles persist browser state such as cookies and local storage across browser sessions. Profiles are scoped to your OpenComputer org; another org cannot list, load, or delete your profiles. Create a profile once: ```typescript TypeScript theme={null} import { BrowserProfile } from "@opencomputer/sdk"; const profile = await BrowserProfile.create({ name: "github-login", }); ``` ```python Python theme={null} from opencomputer import BrowserProfile profile = await BrowserProfile.create(name="github-login") ``` Use the profile when creating a browser: ```typescript TypeScript theme={null} import { Browser } from "@opencomputer/sdk"; const browser = await Browser.create({ profile: { id: profile.id, saveChanges: true, }, headless: false, }); // Log in or update session state, then delete the browser. // With saveChanges enabled, changes are saved back to the profile. await browser.delete(); ``` ```python Python theme={null} from opencomputer import Browser browser = await Browser.create( profile={ "id": profile.id, "save_changes": True, }, headless=False, ) # Log in or update session state, then delete the browser. # With save_changes enabled, changes are saved back to the profile. await browser.delete() ``` Load the same profile later by ID or name: ```typescript TypeScript theme={null} import { Browser, BrowserProfile } from "@opencomputer/sdk"; const profile = await BrowserProfile.connect("github-login"); const browser = await Browser.create({ profile: { id: profile.id, saveChanges: true, }, headless: false, }); ``` ```python Python theme={null} from opencomputer import Browser, BrowserProfile profile = await BrowserProfile.connect("github-login") browser = await Browser.create( profile={ "id": profile.id, "save_changes": True, }, headless=False, ) ``` ## Authentication The SDK uses your OpenComputer API key: ```bash theme={null} export OPENCOMPUTER_API_KEY="osb_..." ``` For local development against a non-production Browser Sessions endpoint, set `OPENCOMPUTER_BROWSER_API_URL`. # Checkpoints Source: https://docs.opencomputer.dev/cli/checkpoint Snapshot, fork, and restore from the CLI ## Creating a Checkpoint Capture the current state of a running sandbox: ```bash theme={null} oc checkpoint create sb-abc123 --name before-migration # or using the shortcut: oc cp create sb-abc123 --name before-migration ``` The checkpoint saves the filesystem and installed state. Status starts as `processing` and transitions to `ready`. ```bash theme={null} oc cp list sb-abc123 # ID NAME STATUS SIZE CREATED # cp-7f3a1b2c before-migration ready 128 MB 2025-01-15T10:30:00Z ``` ## Forking from a Checkpoint Create new sandboxes from a saved checkpoint. Each fork is independent: ```bash theme={null} # Spawn two independent sandboxes from the same checkpoint ID1=$(oc cp spawn cp-7f3a1b2c --json | jq -r '.sandboxID') ID2=$(oc cp spawn cp-7f3a1b2c --json | jq -r '.sandboxID') # Run different experiments oc exec $ID1 --wait -- ./experiment-a.sh oc exec $ID2 --wait -- ./experiment-b.sh ``` Forked sandboxes start with a fresh boot from the saved disk state — don't assume running processes carry over. ## Restoring Revert a sandbox in-place to a checkpoint. All changes since the checkpoint are lost: ```bash theme={null} oc cp restore sb-abc123 cp-7f3a1b2c ``` ## Listing and Deleting ```bash theme={null} oc cp list sb-abc123 oc cp delete sb-abc123 cp-7f3a1b2c ``` Each sandbox can have up to 10 full checkpoints and up to 100 disk-only checkpoints. To create a new checkpoint at the limit, opt into automatic deletion of the oldest eligible checkpoint of the same type: ```bash theme={null} oc cp create sb-abc123 \ --name autosave \ --kind disk_only \ --retention-policy delete_oldest \ --retention-max-count 100 ``` Retention skips public checkpoints, patched checkpoints, and checkpoints that are still referenced by forked sandboxes. ## Checkpoint vs Hibernate | | Checkpoint | Hibernate | | ---------------- | ----------------------------- | ------------------------------ | | Original sandbox | Keeps running | Stopped | | Can fork | Yes — unlimited new sandboxes | No | | Use case | Branching, parallel testing | Pause and resume, cost savings | Use checkpoints when you need to explore multiple paths from the same state. Use hibernation when you just want to pause and resume later. SDK usage: [Checkpoints](/sandboxes/checkpoints). Full flags: [CLI Reference](/reference/cli#oc-checkpoint). # Running Commands Source: https://docs.opencomputer.dev/cli/exec Execute shell commands from the CLI ## Running a Command Use `--wait` to run a command synchronously — output streams to your terminal and the CLI exits with the process exit code: ```bash theme={null} oc exec sb-abc123 --wait -- echo "Hello, World!" oc exec sb-abc123 --wait -- npm run build ``` Without `--wait`, `oc exec` creates an **exec session** and prints the session ID with attach instructions. This is useful for long-running commands: ```bash theme={null} oc exec sb-abc123 -- node server.js # → Session es-xyz created. Attach with: oc exec attach sb-abc es-xyz ``` ## Working Directory & Environment ```bash theme={null} oc exec sb-abc123 --wait --cwd /app --env NODE_ENV=production -- npm run build oc exec sb-abc123 --wait --env API_KEY=xxx --env DEBUG=true -- ./run.sh ``` ## Timeouts Set a timeout with `--timeout` (in seconds). Default is 0 (no timeout). When exceeded, the command is killed: ```bash theme={null} oc exec sb-abc123 --wait --timeout 30 -- npm test ``` ## Creating an Exec Session Plain `oc exec` (without `--wait`) creates a session for long-running commands. The command keeps running even after you disconnect: ```bash theme={null} oc exec sb-abc123 -- python train.py # Session es-abc created ``` ## Capturing Output as JSON Combine `--json` with `--wait` for scripting: ```bash theme={null} RESULT=$(oc exec sb-abc123 --json --wait -- node -e 'console.log("hello")') echo $RESULT | jq '.stdout' # "hello\n" echo $RESULT | jq '.exitCode' # 0 ``` ## Managing Exec Sessions ```bash theme={null} # List active sessions oc exec list sb-abc123 # Kill a session oc exec kill sb-abc123 es-xyz oc exec kill sb-abc123 es-xyz --signal 15 # SIGTERM ``` `oc exec attach` exists as a command but is not yet implemented. It prints guidance to use the SDK or a WebSocket client. Use `oc exec list` and `oc exec kill` to manage sessions from the CLI. ## Shell vs Exec | Use Case | Command | | ---------------------------------- | ------------------------ | | Single command, capture output | `oc exec --wait` | | Scripting and automation | `oc exec --wait --json` | | Long-running background process | `oc exec` (no `--wait`) | | Interactive development, debugging | [`oc shell`](/cli/shell) | SDK usage: [Running Commands](/sandboxes/running-commands). Full flags: [CLI Reference](/reference/cli#oc-exec). # CLI Source: https://docs.opencomputer.dev/cli/overview Manage sandboxes from your terminal The `oc` CLI lets you manage OpenComputer sandboxes directly from your terminal — create sandboxes, run commands, open interactive shells, manage checkpoints, and more. ## Installation ```bash macOS (Apple Silicon) theme={null} curl -fsSL https://github.com/diggerhq/opencomputer/releases/latest/download/oc-darwin-arm64 -o /usr/local/bin/oc chmod +x /usr/local/bin/oc ``` ```bash macOS (Intel) theme={null} curl -fsSL https://github.com/diggerhq/opencomputer/releases/latest/download/oc-darwin-amd64 -o /usr/local/bin/oc chmod +x /usr/local/bin/oc ``` ```bash Linux (x86_64) theme={null} curl -fsSL https://github.com/diggerhq/opencomputer/releases/latest/download/oc-linux-amd64 -o /usr/local/bin/oc chmod +x /usr/local/bin/oc ``` ```bash Linux (ARM64) theme={null} curl -fsSL https://github.com/diggerhq/opencomputer/releases/latest/download/oc-linux-arm64 -o /usr/local/bin/oc chmod +x /usr/local/bin/oc ``` ## Sign in ```bash theme={null} oc login oc whoami ``` `oc login` prints a short confirmation code and opens the hosted sign-in page. Use `oc login --no-browser` over SSH or when a coding agent is running the command for you. The CLI stores the resulting credential in `~/.oc/config.json` with private file permissions; it never prints the key. ```bash theme={null} oc logout # revoke the CLI-created key, then clear it locally oc logout --local # clear local login state without claiming remote revocation ``` `oc login` authenticates the local CLI. CI, servers, and SDK applications should continue to receive an explicit org key through their secret manager and `OPENCOMPUTER_API_KEY`. ### Credential resolution | Priority | Source | Example | | ----------- | --------------------- | --------------------------------------- | | 1 (highest) | CLI flags | `--api-key=xxx` | | 2 | Environment variables | `OPENCOMPUTER_API_KEY` | | 3 | Config file | `oc login` or `oc config set api-key …` | | 4 (lowest) | Defaults | `https://app.opencomputer.dev` | An active `--api-key` or `OPENCOMPUTER_API_KEY` override must be removed before `oc login` or remote `oc logout`; `oc logout --local` can still clear the saved CLI login. Manually setting an API key remains supported: ```bash theme={null} oc config set api-key "$OPENCOMPUTER_API_KEY" oc config show ``` ## Global Flags | Flag | Environment Variable | Description | | ----------- | ---------------------- | -------------------------------- | | `--api-key` | `OPENCOMPUTER_API_KEY` | API key for authentication | | `--api-url` | `OPENCOMPUTER_API_URL` | Control plane URL | | `--json` | — | Output as JSON instead of tables | ## Key Workflows ### Quick Start ```bash theme={null} # Create a sandbox oc create # Run a command and wait for the result oc exec sb-abc123 --wait -- echo "Hello from the cloud" # Open an interactive shell oc shell sb-abc123 # Clean up oc sandbox kill sb-abc123 ``` ### JSON Output & Scripting All commands support `--json` for machine-readable output: ```bash theme={null} # Get a sandbox ID programmatically ID=$(oc create --json | jq -r '.sandboxID') # List all running sandbox IDs oc ls --json | jq -r '.[].sandboxID' # Run a command and capture the result RESULT=$(oc exec $ID --json --wait -- npm test) echo $RESULT | jq '.exitCode' ``` ### Top-level Shortcuts | Shortcut | Expands to | | ----------- | ------------------- | | `oc create` | `oc sandbox create` | | `oc ls` | `oc sandbox list` | | `oc cp` | `oc checkpoint` | ### Create and Shell In ```bash theme={null} oc shell $(oc create --json | jq -r '.sandboxID') ``` ### Hibernate for Cost Savings ```bash theme={null} oc sandbox hibernate sb-abc123 # ... hours later ... oc sandbox wake sb-abc123 oc shell sb-abc123 ``` ### Checkpoint and Fork ```bash theme={null} oc cp create sb-abc --name ready-state ID1=$(oc cp spawn cp-xyz --json | jq -r '.sandboxID') ID2=$(oc cp spawn cp-xyz --json | jq -r '.sandboxID') oc exec $ID1 --wait -- ./test-a.sh oc exec $ID2 --wait -- ./test-b.sh ``` ## Command Reference | Command | Description | | --------------------------------------- | ------------------------------------------ | | [`oc sandbox`](/cli/sandbox) | Create, list, kill, hibernate, wake | | [`oc exec`](/cli/exec) | Run commands, manage exec sessions | | [`oc shell`](/cli/shell) | Interactive PTY terminal | | [`oc checkpoint`](/cli/checkpoint) | Snapshot, fork, restore | | [`oc patch`](/cli/patch) | Checkpoint-attached scripts | | [`oc preview`](/cli/preview) | Expose ports to the internet | | [`oc agent`](/reference/cli/agent) | Invoke durable agents and manage Hook URLs | | [`oc login`](/reference/cli/auth) | Sign in, inspect identity, and log out | | [`oc config`](/reference/cli#oc-config) | Configure API key and API URL | Full flag reference for every command: [CLI Reference](/reference/cli). # Patches Source: https://docs.opencomputer.dev/cli/patch Attach scripts to checkpoints from the CLI ## What Patches Do A patch is a shell script attached to a checkpoint. Patches run in order every time a sandbox is forked from that checkpoint — layer setup without re-creating the checkpoint. ## Creating a Patch From a file or stdin: ```bash theme={null} # From a file oc patch create cp-7f3a1b2c --script ./setup.sh --description "Install deps" # One-liner from stdin echo "npm install -g typescript" | oc patch create cp-7f3a1b2c --script=- ``` ## Layering Setup Add multiple patches — they execute in creation order on every fork: ```bash theme={null} oc patch create cp-7f3a1b2c --script ./install-node.sh --description "Node.js 20" oc patch create cp-7f3a1b2c --script ./app-config.sh --description "App config" # Each spawned sandbox gets both patches applied in order oc cp spawn cp-7f3a1b2c ``` ## Inspecting Patches Review what will run before forking: ```bash theme={null} oc patch list cp-7f3a1b2c # ID SEQ DESCRIPTION STRATEGY CREATED # pa-abc123 1 Node.js 20 on_wake 2025-01-15T10:30:00Z # pa-def456 2 App config on_wake 2025-01-15T10:31:00Z ``` ## Removing a Patch ```bash theme={null} oc patch delete cp-7f3a1b2c pa-abc123 ``` SDK usage: [Patches](/sandboxes/patches). Full flags: [CLI Reference](/reference/cli#oc-patch). # Preview URLs Source: https://docs.opencomputer.dev/cli/preview Expose sandbox ports from the CLI ## Exposing a Port Create a public HTTPS URL that proxies traffic to a port inside your sandbox: ```bash theme={null} oc preview create sb-abc123 --port 3000 # → hostname: sb-abc123-p3000.workers.opencomputer.dev ``` ## Sharing a Dev Server Start a server inside the sandbox, expose it, and share the URL: ```bash theme={null} # Start a dev server in the background oc exec sb-abc123 -- bash -c 'cd /app && npm run dev &' # Expose port 3000 oc preview create sb-abc123 --port 3000 # Get the URL oc preview list sb-abc123 ``` ## Multiple Ports Expose multiple services from the same sandbox: ```bash theme={null} oc preview create sb-abc123 --port 3000 # Frontend oc preview create sb-abc123 --port 8080 # API server oc preview list sb-abc123 # PORT HOSTNAME SSL CREATED # 3000 sb-abc123-p3000.workers.opencomputer.dev active ... # 8080 sb-abc123-p8080.workers.opencomputer.dev active ... ``` ## Custom Domains Pass `--domain` to use your own domain. DNS must point to OpenComputer's ingress, and the domain must be verified in the dashboard. SSL is provisioned automatically. ```bash theme={null} oc preview create sb-abc123 --port 3000 --domain preview.myapp.com ``` ## Cleanup ```bash theme={null} oc preview delete sb-abc123 3000 ``` Preview URLs persist across hibernation/wake cycles — no need to re-create them. ## Bearer-Token Authentication By default, anyone who knows the preview hostname can hit your sandbox's port. To require an `Authorization: Bearer ` header on every request, opt in at create time: ```bash theme={null} oc sandbox create --preview-auth # Created sandbox sb-abc123 (status: running) # Preview auth token (shown once): qx2sSi5IYXWBvnnRqwK9Ky_cIAI-x0Vx1bPCt0XMxsI ``` Then call your preview URL with the token: ```bash theme={null} curl -H "Authorization: Bearer qx2sSi5IYX..." https://sb-abc123-p3000.workers.opencomputer.dev/ ``` Bring your own token if your gateway already has a shared secret: ```bash theme={null} oc sandbox create --preview-auth-token "$GATEWAY_TOKEN" ``` Rotate the token (old one stops working immediately): ```bash theme={null} oc preview rotate-auth sb-abc123 # New preview auth token (shown once): ``` Token is shown exactly once and only its SHA-256 hash is stored. See [Authentication](/sandboxes/preview-urls#authentication) for the SDK equivalents. SDK usage: [Preview URLs](/sandboxes/preview-urls). Full flags: [CLI Reference](/reference/cli#oc-preview). # Sandbox Management Source: https://docs.opencomputer.dev/cli/sandbox Create, manage, and control sandbox lifecycles from the CLI ## Creating a Sandbox `oc create` (shortcut for `oc sandbox create`) provisions a new sandbox VM: ```bash theme={null} oc create oc create --timeout 600 --cpu 2 --memory 2048 --env NODE_ENV=production ``` The sandbox ID is printed on success. Use `--json` to capture it programmatically: ```bash theme={null} ID=$(oc create --json | jq -r '.sandboxID') ``` ## Listing & Inspecting ```bash theme={null} # List all sandboxes oc ls # Detailed info for a specific sandbox oc sandbox get sb-abc123 ``` `oc ls` shows a table with ID, template, status, CPU, memory, and age. Add `--json` for machine-readable output. ## Hibernation & Wake Save state and stop paying for compute. Wake resumes the same sandbox — the platform attempts fast snapshot restore with a cold-boot fallback if needed. ```bash theme={null} oc sandbox hibernate sb-abc123 # ... hours later ... oc sandbox wake sb-abc123 oc shell sb-abc123 ``` The sandbox keeps the same ID across hibernate/wake cycles. Preview URLs remain active. ## Adjusting Timeout The idle timeout resets on every operation (exec, file access, agent activity). Default: 300s. ```bash theme={null} oc sandbox set-timeout sb-abc123 3600 # 1 hour ``` ## Killing a Sandbox ```bash theme={null} oc sandbox kill sb-abc123 ``` All data is lost unless you've created a [checkpoint](/cli/checkpoint) first. ## Common Patterns ### Create and Shell In ```bash theme={null} oc shell $(oc create --json | jq -r '.sandboxID') ``` ### Filter Running Sandboxes ```bash theme={null} oc ls --json | jq '.[] | select(.status == "running") | .sandboxID' ``` ### Batch Cleanup ```bash theme={null} oc ls --json | jq -r '.[].sandboxID' | xargs -I{} oc sandbox kill {} ``` SDK usage: [Sandboxes](/sandboxes/overview). Full flags: [CLI Reference](/reference/cli#oc-sandbox). # Secrets Source: https://docs.opencomputer.dev/cli/secrets Manage secret stores and secrets from the CLI ## Secret stores ```bash theme={null} # Create a store (with optional egress restrictions) oc secret-store create --name my-secrets --egress-allowlist api.anthropic.com # List stores oc secret-store list # Update a store oc secret-store update --egress-allowlist api.anthropic.com,*.openai.com # Delete a store (and all its secrets) oc secret-store delete ``` ## Secrets ```bash theme={null} # Set a secret oc secret set ANTHROPIC_API_KEY sk-ant-... # Set from stdin echo "sk-ant-..." | oc secret set ANTHROPIC_API_KEY --from-stdin # Restrict a secret to specific hosts oc secret set ANTHROPIC_API_KEY sk-ant-... --allowed-hosts api.anthropic.com # List secrets (names only — values are never returned) oc secret list # Delete a secret oc secret delete ANTHROPIC_API_KEY ``` ## Using secrets in sandboxes ```bash theme={null} # Create a sandbox with a secret store oc create --secret-store my-secrets # Env vars are sealed — real values are never in the VM oc exec -- echo '$ANTHROPIC_API_KEY' # osb_sealed_7f3a9c... (not the real key) ``` For how secrets work under the hood, see [Secrets](/sandboxes/secrets). # Shell Source: https://docs.opencomputer.dev/cli/shell Interactive terminal sessions from the CLI ## Opening a Shell ```bash theme={null} oc shell sb-abc123 ``` Opens a full interactive terminal over WebSocket. Press `Ctrl+D` or type `exit` to disconnect. ## Choosing a Shell ```bash theme={null} oc shell sb-abc123 --shell /bin/zsh ``` Default: `/bin/bash`. ## What Works The PTY supports everything you'd expect from a real terminal: * **Editors:** vim, nano * **Monitors:** top, htop * **Tab completion** and command history * **Colors** and ANSI escape codes * **Terminal resizing** — automatic, resize events forwarded Full-screen applications like `tmux` and `less` work correctly. ## Shell vs Exec | Use Case | Command | | ------------------------------------ | ----------------------- | | Run a single command, capture output | `oc exec --wait` | | Scripting and automation | `oc exec --wait --json` | | Interactive development | `oc shell` | | Debugging | `oc shell` | ## Tips ### Create and Shell In ```bash theme={null} oc shell $(oc create --json | jq -r '.sandboxID') ``` ### Shell with Custom Environment Set up the environment before shelling in: ```bash theme={null} ID=$(oc create --json | jq -r '.sandboxID') oc exec $ID --wait -- bash -c 'echo "export PATH=/app/bin:\$PATH" >> ~/.bashrc' oc shell $ID ``` SDK usage: [Interactive Terminals](/sandboxes/interactive-terminals). Full flags: [CLI Reference](/reference/cli#oc-shell). # Agent Skill Source: https://docs.opencomputer.dev/guides/agent-skill Use AI agents to manage cloud sandboxes with natural language The OpenComputer skill lets AI agents like Claude Code create and manage cloud sandboxes using the `oc` CLI. Instead of writing commands yourself, just describe what you want. ## Install ```bash theme={null} npx skills add diggerhq/opencomputer ``` This works with Claude Code, Codex, Cursor, and any agent that supports the [Agent Skills](https://agentskills.io) standard. ## Prerequisites The `oc` CLI must be installed and configured: ```bash theme={null} curl -fsSL https://raw.githubusercontent.com/diggerhq/opencomputer/main/scripts/install.sh | bash oc login ``` If the command prints a browser URL or confirmation code, relay it to the user and wait for them to approve it. The CLI resumes without asking anyone to paste an API key into chat. ## Usage The skill activates automatically when you mention sandboxes or the `oc` CLI. Just ask naturally: **Create a custom base image:** > Create a base image for me that includes a placeholder React app I can use as a starter kit. Checkpoint it so I can spawn copies later. **Fork and experiment:** > Spawn two sandboxes from my react-starter checkpoint. In the first one, add Tailwind CSS. In the second, add Material UI. **Run commands:** > Install PostgreSQL in my sandbox and run the test suite **Manage state:** > Checkpoint my sandbox before I try this migration, so I can roll back if it breaks The agent handles all the `oc` commands under the hood — creating sandboxes, running `exec`, taking checkpoints, applying patches, and cleaning up. # Browser Automation Source: https://docs.opencomputer.dev/guides/browser-automation Run headless and headed browsers inside OpenComputer sandboxes — for scraping, logged-in workflows, and AI-driven automation. OpenComputer sandboxes are full Linux VMs, which means you can run a real Chromium browser inside them the same way you would on a laptop. This guide walks through the setup that makes it *actually work*: the right Chromium flavor, the system libraries you need, the OpenComputer-specific networking quirks, and how to persist browser state across runs. The examples use [**libretto**](https://libretto.sh), an AI-friendly CLI + library on top of Playwright. Everything here applies equally to raw Playwright, Puppeteer, [browser-use](https://browser-use.com/), or [Browserbase](https://www.browserbase.com/) — libretto is just a convenient default. Runnable reference implementation of everything in this guide. CLI + library reference for the browser-automation tool used in the examples. *** ## When to reach for a browser (vs. an API) * The target has no API (flight aggregators, airline sites, most SaaS admin UIs). * The target needs a real logged-in session (cookies + localStorage + JS-challenge cookies). * You want **AI-driven interaction** — describe a task in English, let the agent figure out the clicks. * You need screenshots / visual artefacts for verification. If the site has a good API, use the API. A browser is slower, heavier, and flakier. But when you need it, OpenComputer gives you real VMs — not containers — so you can run full Chromium with no Docker-flavored limitations. *** ## Step 1: Build a snapshot with Chromium pre-installed Browser setup is heavy (apt packages + Chromium binary is \~500MB). Bake it into a [named snapshot](/sandboxes/snapshots) once, launch sandboxes from it in seconds. ```typescript build-snapshot.ts theme={null} import { Image, Snapshots } from "@opencomputer/sdk/node"; // Runtime deps Chromium links against on Ubuntu 22.04. Matches Playwright's // published dependency list. const CHROMIUM_DEPS = [ "libnss3", "libnspr4", "libatk1.0-0", "libatk-bridge2.0-0", "libcups2", "libdrm2", "libxkbcommon0", "libxcomposite1", "libxdamage1", "libxfixes3", "libxrandr2", "libxext6", "libgbm1", "libpango-1.0-0", "libcairo2", "libasound2", "fonts-liberation", // libnss3-tools gives us `certutil` — needed at runtime to trust OC's // egress-proxy CA in Chromium (Chromium ignores SSL_CERT_FILE). "libnss3-tools", ]; const image = Image.base() .aptInstall(CHROMIUM_DEPS) .workdir("/home/sandbox") .runCommands( "cd /home/sandbox && npm init -y >/dev/null", // Install libretto + AI adapter locally so `npx libretto` resolves without // a cold npm fetch at runtime. "cd /home/sandbox && npm install --no-audit --no-fund libretto @ai-sdk/anthropic", // Playwright downloads its own Chromium build — we want headless-shell // for lightweight scraping, or full chromium for headed/VNC scenarios. "cd /home/sandbox && npx --yes playwright install chromium-headless-shell", ); const snapshots = new Snapshots(); await snapshots.create({ name: "browser", image }); ``` **Don't apt-install `chromium-browser` on Ubuntu 22.04.** That package is a snap shim that won't run in a minimal VM. Either install Google Chrome via its apt repo (`google-chrome-stable`), or rely on Playwright's bundled Chromium (recommended — it's purpose-built for automation). *** ## Step 2: Per-boot setup that can't be baked in A few things need to run at sandbox startup rather than during snapshot build, because they depend on per-sandbox state. ```typescript launch.ts theme={null} import { Sandbox, SecretStore } from "@opencomputer/sdk/node"; const sandbox = await Sandbox.create({ snapshot: "browser", envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY!, // Keep Playwright/Chromium off the small tmpfs /dev/shm and /tmp — // write profile dirs, cache, everything to the data disk. TMPDIR: "/home/sandbox/tmp", }, secretStore: "browser-egress", // see "Networking" section below }); // Populate /etc/hosts — the guest kernel sets up /etc/resolv.conf but not // /etc/hosts, and libretto's Playwright CDP client hardcodes http://localhost // which will ENOTFOUND without this. await sandbox.commands.run( "grep -q 'localhost' /etc/hosts || " + "(printf '127.0.0.1 localhost\\n::1 localhost\\n' | sudo tee -a /etc/hosts)", ); // Trust OC's egress-proxy CA in Chromium's NSS store. Chromium ignores // SSL_CERT_FILE / NODE_EXTRA_CA_CERTS — the env vars OC sets for libraries // have no effect on the browser's TLS validation. await sandbox.commands.run( [ "mkdir -p /home/sandbox/.pki/nssdb", "certutil -d sql:/home/sandbox/.pki/nssdb -N --empty-password || true", "certutil -d sql:/home/sandbox/.pki/nssdb -A -n opensandbox-proxy -t 'TC,C,T' " + "-i /usr/local/share/ca-certificates/opensandbox-proxy.crt", ].join(" && "), ); await sandbox.commands.run("mkdir -p /home/sandbox/tmp && chmod 700 /home/sandbox/tmp"); ``` *** ## Networking: the `secretStore` requirement This is the single most common gotcha. A sandbox without a `secretStore` attached **has no outbound egress at all** — every HTTPS request gets a `407` from the internal proxy. OpenComputer routes all outbound traffic through a secrets-injection proxy. The proxy only accepts traffic from sandboxes that have at least one sealed secret registered, because session registration happens as part of sealing secrets. The workaround is to create a `SecretStore` with a wildcard egress allowlist and at least one (possibly dummy) entry: ```typescript theme={null} import { SecretStore } from "@opencomputer/sdk/node"; const stores = await SecretStore.list(); let store = stores.find((s) => s.name === "browser-egress"); if (!store) { store = await SecretStore.create({ name: "browser-egress", egressAllowlist: ["*"], // wildcard — scope this down for production }); } await SecretStore.setSecret(store.id, "PLACEHOLDER", "not-used-just-triggers-session"); const sandbox = await Sandbox.create({ snapshot: "browser", secretStore: "browser-egress", }); ``` With the store attached, outbound traffic flows normally and the `opensandbox-proxy.crt` we trusted earlier lets Chromium validate the MITM-rewritten certs. *** ## Step 3: Run a headless browser With the snapshot and per-boot setup in place, driving the browser is a normal libretto session: ```typescript theme={null} import { execFile } from "node:child_process"; import { promisify } from "node:util"; const run = promisify(execFile); // Open a page in a named session — libretto persists cookies/localStorage // per session name in .libretto/sessions//. await run("npx", ["libretto", "open", "https://example.com", "--session", "demo", "--headless"], { cwd: "/home/sandbox", }); // AI snapshot — Claude analyzes the page and returns a summary + selectors. const { stdout } = await run("npx", [ "libretto", "snapshot", "--session", "demo", "--objective", "Describe the main content and interactive elements.", "--context", "Freshly loaded page", ], { cwd: "/home/sandbox", maxBuffer: 8 * 1024 * 1024 }); console.log(stdout); ``` The `snapshot` command requires an `ANTHROPIC_API_KEY` (or an OpenAI / Gemini / Vertex key — configure via `.libretto/config.json`). It's the AI-driven feature that makes libretto different from raw Playwright. *** ## Step 4: Run a headed browser (for interactive login) For workflows where the user needs to log in themselves, you can render the browser to a virtual display and expose it via VNC. This lets you embed the running browser in a web UI. Install the VNC stack at runtime (same no-rebuild pattern as per-boot setup): ```typescript theme={null} await sandbox.commands.run( "sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq xvfb x11vnc novnc websockify", ); // Start Xvfb → x11vnc → websockify as long-lived exec sessions so they // persist across individual commands. const xvfb = await sandbox.exec.start("Xvfb", { args: [":99", "-screen", "0", "1280x800x24", "-ac"], }); await new Promise((r) => setTimeout(r, 1500)); const x11vnc = await sandbox.exec.start("x11vnc", { args: ["-display", ":99", "-forever", "-shared", "-nopw", "-rfbport", "5900", "-quiet"], }); const websockify = await sandbox.exec.start("websockify", { args: ["--web=/usr/share/novnc/", "6080", "localhost:5900"], }); ``` Set `DISPLAY=:99` in the sandbox envs when you create it, then open with `--headed`: ```typescript theme={null} await run("npx", ["libretto", "open", "https://app.example.com", "--session", "login", "--headed"], { cwd: "/home/sandbox", env: { ...process.env, DISPLAY: ":99" }, }); ``` The VNC WebSocket is available on port 6080 — get its preview URL with `sandbox.getPreviewDomain(6080)` and embed `