Docs

offstage cloud gives your code and your agents a real Mac over an API. A sandbox is a macOS 27 user account on Apple silicon that you hold and drive one call at a time. A task is fire-and-forget CI: clone a repository, run a command, collect the artifacts.

Quickstart

  1. Get an API key

    Create an account and get $5 in free credit, with no credit card needed. Create a key under API keys. It starts with sk_offstage_ and is shown once.

    Shell
    export OFFSTAGE_API_KEY=sk_offstage_...
  2. Install the SDK

    Install the current SDK from the versioned tarball below; the npm registry release is older. TypeScript compiled to ESM, with no runtime dependencies. It runs on Node 18+, Bun and Deno.

    Shell
    npm install https://api.offstage.cloud/sdk/offstage-cloud-0.3.1.tgz
  3. Run something on a Mac

    Create a sandbox, run commands, write a file, then kill it. A sandbox bills by the second until it is killed, so kill it in a finally.

    first-mac.mjs
    import { Sandbox } from "offstage-cloud"
    
    const sandbox = await Sandbox.create({ timeoutMs: 10 * 60_000 })
    try {
      const version = await sandbox.commands.run("sw_vers -productVersion")
      console.log("macOS", version.stdout.trim(), "exit", version.exitCode)
    
      await sandbox.files.write("hello.swift", 'print("hello from a mac")')
      const run = await sandbox.commands.run("swift hello.swift")
      console.log(run.stdout, run.exitCode) // a non-zero exit does not throw
    } finally {
      await sandbox.kill()
    }

Set up from your agent

Coding agents can set themselves up from one file, SKILL.md. It covers the key handoff, the install, paths and cleanup, then has the agent run one command on a real Mac and report what it verified. Paste this into your agent:

Read https://api.offstage.cloud/SKILL.md and follow it to set up offstage cloud, so you can run builds, tests and commands on a real Mac.

GitHub Actions

Run your workflows on a Mac by changing runs-on. Each job gets a fresh macOS account on a Mac Studio, GitHub's runner runs that one job, and the account is wiped when it ends.

  1. Connect GitHub

    In the console, open CI, click Connect GitHub and install the offstage cloud app on your GitHub organization. Its jobs bill to the team you connected it from.

  2. Change one line

    .github/workflows/ci.yml
    on: [push, pull_request]
    jobs:
      test:
        runs-on: offstage-macos
        steps:
          - uses: actions/checkout@v7
          - run: swift test

    Public CI currently uses offstage-macos. The offstage-macos-gui label requires desktop access, which is paused for public accounts (gui-paused).

  • A job bills per second at the sandbox price, from the moment a Mac takes it until it ends. Waiting for a Mac is free.
  • A job runs for at most 60 minutes.
  • Public repositories use your organization's runners only if its runner group allows them (GitHub: Settings, Actions, Runner groups).

Sandboxes

A sandbox is a whole macOS user account that you drive one call at a time. Every call takes { apiKey?, baseUrl? }, which default to OFFSTAGE_API_KEY and https://api.offstage.cloud.

CallWhat it does
Sandbox.create({ session?, timeoutMs?, idleTimeoutMs? })Makes a sandbox. session is "headless" (the default) or "gui". It lives 1 hour by default and never more than 24 hours; it closes after 10 idle minutes by default.
sandbox.commands.run(cmd, { cwd?, envs?, timeoutMs? })Runs a command in zsh -lc and returns stdout, stderr, exitCode and durationMs. Long commands are followed for you, up to 30 minutes.
sandbox.files.write(path, data)Writes up to 10 MiB, replacing any file there. Paths are relative to the sandbox.
sandbox.files.read(path)Reads a file back, as text or byte-exact with { format: "bytes" }.
sandbox.screenshot({ maxDimension? })A PNG of the screen, in gui sandboxes.
sandbox.runAgent({ goal, maxSteps?, maxDurationMs? })Hands a gui sandbox's screen a goal, which Claude carries out on the Mac with your Anthropic API key. Returns the verdict, every step and its screenshots.
sandbox.replayUrl, sandbox.recording()A gui sandbox is recorded: its screen, commands and agent steps. replayUrl is the console page that shows it live and plays it back after it closes, for 7 days. Create it with record: false to keep none.
sandbox.setTimeout(ms)Keeps it alive ms from now, within 24 hours of creation.
sandbox.kill()Closes it and stops the clock.
Sandbox.connect(id), Sandbox.list()Picks up a sandbox by id, or lists your team's.

Public availability: headless sandboxes are available. Interactive desktop access (session: "gui"), screen capture and runAgent are currently paused for public accounts and return gui-paused.

A headless sandbox can take work as soon as a Mac boots. A gui sandbox runs in the account's logged-in desktop session, for screenshots and apps with windows, and can wait for a slot. There is no click or typing API. To drive a UI, hand the sandbox a goal with runAgent: a harness inside the slot carries it out, so no click or key crosses the network. Or drive it with a program that runs on the Mac. That program holds no screen or input permission of its own; it asks the slot's session daemon for screenshots and input, over the socket named in OFFSTAGE_SESSION_SOCKET.

Tasks

A task clones a public repository's default branch, runs your command in it and keeps the artifacts you name. It bills from start to finish; waiting in the queue is free.

ci.mjs
import { Task } from "offstage-cloud"

const task = await Task.create({
  repo: "https://github.com/org/app",
  kind: "build",
  command: "set -o pipefail; swift build 2>&1 | tee build.log",
  artifacts: ["build.log"],
})
const done = await task.wait() // never throws on a failed task
if (done.failureReason === "exit") console.log("the build is broken")

There is no ref or env option: put git fetch --depth 1 origin <ref> && git checkout FETCH_HEAD && … or FOO=bar … in the command. Branch CI on failureReason:

failureReasonMeaning
exitThe command exited non-zero: the build or tests are broken.
setupThe clone failed, or the command exited 0 without a promised artifact.
timeoutIt ran past timeoutMs.
no-capacityNo Mac took it within queueTimeoutMs. It never ran and cost nothing.
box-lost, plane-restart, never-startedThe fleet lost it. It is not billed; submit it again.

What's on the Mac

  • macOS 27 on a Mac Studio with M5 Max (Apple silicon, arm64).
  • Xcode 27.2 and the iOS 27.2 simulator: xcodebuild, swift build, swift run and swift test support command-line builds and Swift package tests. xcrun simctl can boot a simulator, install and launch an app, and screenshot it.
  • Simulator create, boot and screenshot operations have been measured in headless sandboxes. We have not verified a complete iOS Simulator xcodebuild test run; validate your project before relying on it. Use swift test for Swift package tests.
  • Swift and SwiftPM, Apple clang, git and make. Check a version with a command before you depend on it.
  • No sudo and no Homebrew: commands run as the account's own user, so install tools into the project or the home directory. The network is available.

Errors

Every failure is an OffstageError with a status, a stable code (such as sandbox-closed, bad-path or concurrency-limit), a message, a fix that says what to do, and the plane's requestId. isOffstageError(e) tests for one. Over HTTP every error has the shape {"error": {"code", "message", "fix", "requestId"}}.

Creates send an Idempotency-Key and retry network errors and 502, 503 and 504 answers, so a lost response never makes a second sandbox. A command is never started twice.

Pricing and limits

  • $1.10 an hour for each sandbox or task, billed per second, rounded up to the whole second, with no minimum. A 10-minute run costs about $0.18.
  • A sandbox bills from the moment a Mac takes it until it closes. A task bills from start to finish. Waiting in the queue is free.
  • New personal accounts get $5 in free credit. No credit card is needed to use it. Paid top-ups are not open yet; contact us for more credit when it runs out.
  • A team can hold 4 open sandboxes at once, independently of its task limit. Up to 4 tasks or CI jobs run at once; 16 more can queue. A fifth sandbox gets concurrency-limit; jobs beyond the running limit queue.
  • Up to 8 sandboxes share a Mac Studio M5 Max with 18 CPU cores and 36 GB of memory. There is no fixed CPU or memory allocation per sandbox.
  • Every new team starts with a $50 monthly budget, with alerts at 50%, 80% and 100%. The owner can raise or lift it in the console.
  • Creating work reserves its worst case, its timeoutMs at $1.10 an hour, until it ends. Ask for the timeout you need, not the maximum.

Reference