Skip to content

Agent Environments ​

Templates define runnable Linux environments. Their package pins, authentication variables, image delivery, and readiness are derived from the same catalog used by sandbox creation; a catalog entry is not a claim that every remote experience is available end-to-end.

python
sandbox = client.sandboxes.create(
    template="claude-code",
    env={"ANTHROPIC_API_KEY": key},
)
result = sandbox.run('claude "refactor the auth module"')

List templates with client.templates.list() or GET /sandboxes/templates. The public GET /capabilities endpoint and capability manifest expose the same readiness records and current feature gates. Readiness describes code and configuration, not live production health or proof that an image was published.

Coding agents ​

TemplateCLIAuth (via env)Readiness
claude-codeClaude CodeANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEYdegraded
codexOpenAI CodexOPENAI_API_KEY; endpoint/model via OPENAI_BASE_URL / OPENAI_MODELdegraded
gemini-cliGemini CLIGEMINI_API_KEY; endpoint/model via GEMINI_BASE_URL / GEMINI_MODELdegraded
deepseek-harnessDeepSeek HarnessDEEPSEEK_API_KEYdegraded
qwen-codeQwen CodeOPENAI_API_KEY; endpoint/model via OPENAI_BASE_URL / OPENAI_MODELdegraded
kimi-cliKimi CLIKIMI_API_KEY; endpoint/model via KIMI_BASE_URL / KIMI_MODEL_NAMEdegraded

Compute ​

TemplateWhat you getReadiness
pythonStandard Python runtime for scripts and notebooks.available
nodeNode.js runtime for JavaScript and TypeScript workloads.available
ubuntuBare Ubuntu environment with a shell.available
code-interpreterPrebuilt Jupyter-style code interpreter with execd.available

Browser ​

TemplateWhat you getReadiness
playwrightHeadless browser automation with Playwright and Chromium.available
chromeChromium with VNC and remote DevTools on port 9222.degraded

Desktop ​

TemplateWhat you getReadiness
vscodecode-server (VS Code in the browser), started on a port you choose.requires_ingress
desktopXFCE desktop with Xvfb, x11vnc and noVNC preinstalled.requires_ingress

Agent CLI versions are pinned in the template catalog. degraded means the template runs with a known limit: an agent CLI that installs at container start because no prebuilt Ovrin agent image registry is configured, or a remote interface the image does not expose. requires_ingress means the sandbox works internally but its interface is a page opened in a browser, which needs browser previews. A deployment with a preview domain reports those templates as available; ask it with GET /capabilities.

Remote interfaces ​

There are two ways to reach a port inside a sandbox from outside it.

Endpoints are for programs. sandbox.endpoint(port) returns an HTTP URL and a WebSocket URL through the control plane. They take the same Authorization: Bearer header as the rest of the API, which scripts, agents and automation libraries can send and a browser tab cannot.

Browser previews are for people. sandbox.preview(port) publishes the port on a private address of its own and returns a one-time link that signs a browser in to it.

For the three templates with a remote interface of their own:

TemplateInterfaceHow to reach it
chromeDevTools (CDP) on 9222sandbox.devtools(), from a script or agent, where the deployment serves Ovrin's Chrome image (chrome reported available). Otherwise from inside the sandbox.
vscodecode-server, in a browserA browser preview.
desktopnoVNC, in a browserA browser preview.

See Chrome, VS Code and Desktop for what each one does today.

Browser previews ​

python
preview = sandbox.preview(8443)

preview["url"]       # https://<random>.<preview domain>/ -- the preview's address
preview["open_url"]  # the link that opens it

Open open_url in a browser. The link works once and expires within a minute; following it signs that browser in and lands on whatever the sandbox is serving on the port. From then on pages and WebSockets load as they would from any site, until the preview expires or you close it.

  • Private. A preview opens only through a link, and only someone holding your account's credential can ask for one. The address on its own opens nothing, and neither does your API key.
  • Bounded. One hour by default; pass ttl_seconds for longer, up to the deployment's maximum. Deleting the sandbox closes its previews.
  • Revocable. client.sandboxes.close_preview(preview["id"]) cuts off every browser signed in to it, at once. client.sandboxes.preview_link(id) gives another one-time link, for a second device or a later session.
  • One port. A preview reaches the port it was made for and nothing else in the sandbox.
  • The application's own logins work. Its cookies, Authorization headers, HEAD and OPTIONS requests reach it unchanged; only the preview's own session is kept from it. It sees its sandbox address as Host, so development servers that check the host accept it, and the public address in X-Forwarded-Host and X-Forwarded-Proto. Use relative URLs, or those headers, when it builds links to itself.
  • Not cached on the way. Responses are marked private, so no CDN or shared cache keeps one for the next visitor. The browser still caches what the application allows.

Anyone who can use a signed-in browser can use what the port serves. For an editor or a desktop that means running commands in the sandbox, so close previews you are done with.

Previews are a deployment feature. GET /capabilities reports browser_preview_urls as active where they are on, and the request returns 501 where they are not.

WebSocket endpoints ​

A WebSocket server listening on 0.0.0.0 inside a sandbox can be reached from outside it. Ask for the endpoint, then connect with your API key in the handshake:

python
import asyncio
import os

import ovrin
import websockets

client = ovrin.Client()  # reads OVRIN_API_KEY
endpoint = client.sandboxes.endpoint("sbx_...", 8765)


async def main():
    headers = {"Authorization": f"Bearer {os.environ['OVRIN_API_KEY']}"}
    async with websockets.connect(
        endpoint["websocket_url"] + "chat", additional_headers=headers
    ) as socket:
        await socket.send("hello")
        print(await socket.recv())


asyncio.run(main())

Text and binary frames, subprotocols, the path and the query string all pass through unchanged. Your API key does not: it is removed before anything is forwarded to the sandbox.

A refused handshake is an HTTP status rather than a socket that opens and closes:

StatusMeaning
401 / 403No credential, or a key whose scope does not cover sandboxes
404Not a sandbox in your account
429Too many sockets open on the account, or handshakes arriving too fast
502Nothing is answering on that port

Messages are capped at 16 MiB (close code 1009). A socket is closed with 1008 once the key or session that opened it is revoked or expired.

Passing secrets ​

Prefer not to hand long-lived provider keys through env where you can bind them at the network boundary instead — see Secrets & Credential Vault. Today that path is not available yet; env injection is the supported default.