# Go SDK One Go client for every Runtime Cloud product, with a `context.Context` on every call. The module is `withruntime.com/go`, imported as package `withruntime`. It needs Go 1.23 or later and uses only the standard library. ```bash no-run go get withruntime.com/go ``` **The client finds its key by itself.** It uses `RUNTIME_API_KEY` when that is set. Otherwise, it uses the connection this machine saved. Any `npx withruntime` command sets up that connection, with one browser approval. On a server, store a key from https://withruntime.com/account/keys in `RUNTIME_API_KEY`, from your secret manager. Never put it in source code, a URL or a command-line argument. With no key anywhere, `withruntime.New` returns an error. Its code is `missing_api_key`, and its hint says how to get one. ## Hello, sandbox ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, &withruntime.CreateOptions{}) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) if err := sbx.Files.Write(ctx, "/workspace/invoice.py", []byte("print(sum([125, 250, 375]))\n")); err != nil { log.Fatal(err) } result, err := sbx.Exec(ctx, "python3 /workspace/invoice.py", &withruntime.ExecOptions{Check: true}) if err != nil { log.Fatal(err) } fmt.Print(result.Stdout) } ``` `Create` returns once the sandbox is running. `opts` may be `nil`. With no options, you get the free usage while it lasts, the default region, and 2 vCPU, 4 GiB of memory and a 4 GiB disk. The sandbox runs while it works and pauses itself when idle, with no time limit. `TimeoutSeconds` sets one, from 60 to 86,400. `EndsAt` is nil while none applies. The free usage is spent first, then prepaid credit. `Funding` is accepted and ignored. Every field is optional: ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, &withruntime.CreateOptions{ Name: "tests-42", Labels: map[string]string{"team": "search", "job": "42"}, VCPU: 2, MemoryMiB: 4096, DiskMiB: 8192, TimeoutSeconds: 900, OnLeaseEnd: "stop", Network: &withruntime.Network{ Internet: true, Allow: []string{"proxy.golang.org", "*.githubusercontent.com"}, }, }) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) fmt.Println(sbx.ID(), sbx.Info().Funding, sbx.Info().ExpiresAt) } ``` A field newer than the SDK goes in `Extra`. The SDK merges `Extra` into the request. ## Run commands `Exec` runs a string under `bash -c`. `ExecArgv` runs a program directly, with no shell. Use it for untrusted arguments. ```go package main import ( "context" "fmt" "log" "os" "time" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, nil) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) if _, err := sbx.Exec(ctx, "mkdir -p app && echo 'print(1 + 1)' > app/main.py", nil); err != nil { log.Fatal(err) } run, err := sbx.ExecArgv(ctx, []string{"python3", "main.py"}, &withruntime.ExecOptions{ Cwd: "/workspace/app", Env: map[string]string{"API_TOKEN": os.Getenv("API_TOKEN")}, Timeout: 2 * time.Minute, }) if err != nil { log.Fatal(err) } if *run.ExitCode != 0 { fmt.Println(run.Stderr) } fmt.Print(run.Stdout) } ``` - `Env` is how secrets reach a command. It is never echoed back, and journals record a hash, not the value. Never put a secret in the command line itself. - `Stdin` gives the command input, then closes it. - `User: "root"` runs the command as root, with no `sudo` in front, keeping its `Env` and `PATH`; `HOME` is `/root`. `SpawnOptions` and `TerminalOptions` take it too, and `ProcessInfo.User` says which a process runs as. - The default timeout is 60 seconds; the maximum is 24 hours. A timeout is a result (`TimedOut` is true, with the output so far), not an error. - `Check: true` returns a `*withruntime.CommandError`, with the exit code and output, on a non-zero exit or a timeout. - A result holds all of `Stdout` and `Stderr`, up to 256 MiB of each; with `OnStdout` or `OnStderr` there is no limit. `StdoutBytes` and `StderrBytes` give the bytes each stream wrote. `MaxOutputBytes` keeps at most that many bytes of each stream; `StdoutTruncated`, `StderrTruncated` and `Truncated` are then true for a stream that was cut. With `OnStdout` or `OnStderr`, or a `Timeout` over 60 seconds, the command streams instead. Stream output as it happens with callbacks, or range over the events: ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, nil) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) _, err = sbx.Exec(ctx, "for i in 1 2 3; do echo line $i; sleep 1; done", &withruntime.ExecOptions{ OnStdout: func(text string) { fmt.Print(text) }, }) if err != nil { log.Fatal(err) } for event, err := range sbx.ExecStream(ctx, "node --version", nil) { if err != nil { log.Fatal(err) } switch event.Type { case "stdout": fmt.Print(event.Data) case "exit": fmt.Println("exit", *event.ExitCode) } } } ``` A long stream resumes by itself when the server ends it, so no output is dropped. The sandbox keeps the latest 1 MiB of a command's output. A reader that falls further behind gets a `truncated` event. ## Background processes ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, nil) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) server, err := sbx.Spawn(ctx, "python3 -m http.server 8000", &withruntime.SpawnOptions{Cwd: "/workspace"}) if err != nil { log.Fatal(err) } repl, err := sbx.SpawnArgv(ctx, []string{"python3", "-i", "-q"}, &withruntime.SpawnOptions{PipeStdin: true}) if err != nil { log.Fatal(err) } if err := repl.Write(ctx, []byte("print(21 * 2)\n"), true); err != nil { log.Fatal(err) } done, err := repl.Wait(ctx) if err != nil { log.Fatal(err) } fmt.Print(done.Stdout) processes, err := sbx.Processes(ctx) if err != nil { log.Fatal(err) } for _, process := range processes { fmt.Println(process.ID, process.State, process.Command) } if err := server.Kill(ctx, "SIGTERM"); err != nil { log.Fatal(err) } } ``` `process.Output(ctx, 0)` yields every event from the start until the process exits. A process outlives your connection. Get it back with `sbx.Process(ctx, id)`. `Write` tracks input offsets, so a retried write is never typed twice. ## Files ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, nil) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) if err := sbx.Files.Write(ctx, "/workspace/data/input.csv", []byte("a,b\n1,2\n")); err != nil { log.Fatal(err) } text, err := sbx.Files.ReadText(ctx, "/workspace/data/input.csv") if err != nil { log.Fatal(err) } fmt.Print(text) entries, err := sbx.Files.List(ctx, "/workspace", &withruntime.FileListOptions{Depth: 2}) if err != nil { log.Fatal(err) } for _, entry := range entries { fmt.Println(entry.Type, entry.Size, entry.Path) } if err := sbx.Files.Mkdir(ctx, "/workspace/out", true); err != nil { log.Fatal(err) } if err := sbx.Files.Rename(ctx, "/workspace/data/input.csv", "/workspace/out/input.csv", false); err != nil { log.Fatal(err) } if err := sbx.Files.Upload(ctx, "./testdata", "/workspace/project"); err != nil { log.Fatal(err) } if err := sbx.Files.Download(ctx, "/workspace/out", "./out"); err != nil { log.Fatal(err) } } ``` `Write` makes parent directories and replaces the file atomically. A file over 1 MiB goes in parallel chunks, checked by SHA-256. `Stat` returns `nil` for a path that does not exist. `Upload` and `Download` move a whole directory as one gzipped tar archive. They use the API's folder routes, which pack and unpack it with `tar` inside the sandbox ([files in the API guide](./api#files)). A large one uploads in parts. A downloaded folder is unpacked as it arrives. It lands in place only once it has all arrived. A download cut short fails with `download_incomplete` and writes nothing. ### Links A link is a secret URL that anyone can open, with no API key. A GET downloads the file. Range works, so videos play and downloads resume. A PUT with the raw body uploads the file atomically. A link expires in 1 hour by default, and at most 7 days. `RevokeLinks` ends every link made for the sandbox. Revoking the key that made a link also ends its links. ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, nil) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) down, err := sbx.Files.DownloadURL(ctx, "/workspace/report.csv", &withruntime.DownloadURLOptions{ Filename: "report.csv", ExpiresInSeconds: 600, }) if err != nil { log.Fatal(err) } up, err := sbx.Files.UploadURL(ctx, "/workspace/incoming.bin", &withruntime.UploadURLOptions{MaxBytes: 10 << 20}) if err != nil { log.Fatal(err) } fmt.Println(down.URL, down.ExpiresAt, up.URL) if _, err := sbx.Files.RevokeLinks(ctx); err != nil { log.Fatal(err) } } ``` ## Pause, wake, extend, fork and snapshot ```go package main import ( "context" "fmt" "log" "time" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, &withruntime.CreateOptions{}) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) if err := sbx.Pause(ctx, nil); err != nil { // memory and files are kept; compute billing stops log.Fatal(err) } again, err := client.Sandboxes.Get(ctx, sbx.ID()) if err != nil { log.Fatal(err) } if err := again.Wake(ctx, 20*time.Minute, nil); err != nil { log.Fatal(err) } if err := again.Extend(ctx, 10*time.Minute, nil); err != nil { log.Fatal(err) } copies, err := again.Fork(ctx, &withruntime.ForkOptions{Count: 2}) if err != nil { log.Fatal(err) } for _, copied := range copies { fmt.Println("fork", copied.ID(), copied.State()) _ = copied.Stop(ctx, nil) } snapshot, err := again.Snapshot(ctx, &withruntime.SnapshotOptions{Name: "ready", RetentionDays: 7}) if err != nil { log.Fatal(err) } fromSnapshot, err := client.Sandboxes.Create(ctx, &withruntime.CreateOptions{Snapshot: snapshot.ID}) if err != nil { log.Fatal(err) } _ = fromSnapshot.Stop(ctx, nil) if err := client.Snapshots.Delete(ctx, snapshot.ID); err != nil { log.Fatal(err) } } ``` A fork copies a sandbox as it is now, with its files, memory and running processes. It runs on its server while there is room, and on another server when it is full. See [JavaScript](./javascript) for what forks and snapshots keep and how they are billed. If a copy fails, the error's `Details["startedSandboxIds"]` names the copies that did start. ## Share a port ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, nil) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) if _, err := sbx.Spawn(ctx, "python3 -m http.server 3000", nil); err != nil { log.Fatal(err) } preview, err := sbx.Previews.Create(ctx, 3000, nil) if err != nil { log.Fatal(err) } fmt.Println(preview.URL, *preview.Token) // send the token as x-runtime-preview-token } ``` A preview is private by default. `Visibility: "public"` shares it with anyone who has the address. Addresses are under `runtimehost.com`. ## Sandboxes by name, and keeping one running `Sandboxes.GetOrCreate(ctx, name, opts)` returns the sandbox with that name. It wakes the sandbox if paused, and restarts it if stopped and persistent. Otherwise it creates one with `opts`. `Info().Reused` says which. A sandbox with no time limit runs while it works, with nothing to renew. `Persistent: true` keeps a paid sandbox running until you stop it, idle or not, while credit lasts. `KeepAlive` moves a time limit on from your process, keeping ten minutes ahead, until `Stop` or `StopKeepAlive`. On a sandbox with no limit, it only watches. ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } dev, err := client.Sandboxes.GetOrCreate(ctx, "dev", &withruntime.CreateOptions{IdlePauseSeconds: 900}) if err != nil { log.Fatal(err) } fmt.Println(dev.ID(), dev.Info().Reused) dev.KeepAlive(ctx, nil) defer dev.StopKeepAlive() } ``` ## An interactive terminal `sbx.Terminal` opens a shell over a WebSocket. It is an `io.ReadWriteCloser`: write what you type, read what it prints. ```go package main import ( "context" "io" "log" "os" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, nil) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) term, err := sbx.Terminal(ctx, &withruntime.TerminalOptions{Cols: 120, Rows: 40}) if err != nil { log.Fatal(err) } defer term.Close() if _, err := term.Write([]byte("uname -a; exit\n")); err != nil { log.Fatal(err) } if _, err := io.Copy(os.Stdout, term); err != nil { log.Fatal(err) } } ``` ## Images, volumes and snapshots ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } image, err := client.Images.Build(ctx, withruntime.CreateImageOptions{ Name: "data", Recipe: &withruntime.ImageRecipe{Pip: []string{"pandas"}, Apt: []string{"jq"}}, }, func(line withruntime.ImageLogLine) { fmt.Println(line.Text) }) if err != nil { log.Fatal(err) } volume, err := client.Volumes.Create(ctx, withruntime.CreateVolumeOptions{SizeMiB: 10240, Name: "cache"}) if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, &withruntime.CreateOptions{ Image: image.ID, Volumes: []withruntime.VolumeMount{{VolumeID: volume.ID, Path: "/data"}}, }) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) result, err := sbx.Exec(ctx, "python3 -c 'import pandas; print(pandas.__version__)'", nil) if err != nil { log.Fatal(err) } fmt.Print(result.Stdout) } ``` `Images.Build` waits until the image is ready. If the build fails, it returns an `*Error` whose code is `image_failed`. `Images.Create` queues the build and returns. A Dockerfile build takes `Dockerfile` and `ContextDir`. `ContextDir` is the folder `docker build` would read, with its `.dockerignore`. Only the parts of the folder that the server does not have yet are uploaded. `Images.Resolve`, `Tag`, `Untag`, `Versions`, `FollowLogs` and `Images.Registries.Set` cover private images and other image tasks. See [custom images](./images). A volume lives on one server. Daily backups are enabled by default. `BackedUp` becomes true after a backup has been copied off the host and checked. A sandbox with volumes cannot be snapshotted. ## Code interpreter, network rules and secrets ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, nil) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) if _, err := sbx.Interpreter.Run(ctx, "import math\nx = math.pi", nil); err != nil { log.Fatal(err) } cell, err := sbx.Interpreter.Run(ctx, "round(x * 2, 3)", nil) if err != nil { log.Fatal(err) } fmt.Println(cell.Results[0].Data["text/plain"]) policy, err := sbx.Network.Set(ctx, withruntime.Network{Internet: true, Allow: []string{"pypi.org", "*.pythonhosted.org"}}) if err != nil { log.Fatal(err) } fmt.Println(policy.Enforced, policy.Allow) secret, err := client.Secrets.Set(ctx, "OPENAI_API_KEY", withruntime.SetSecretOptions{ Value: "sk-...", Hosts: []string{"api.openai.com"}, }) if err != nil { log.Fatal(err) } fmt.Println(secret.Placeholder) // what sandboxes see in $OPENAI_API_KEY } ``` `sbx.Desktop` drives a Linux desktop in the sandbox (`Start`, `Open`, `Click`, `Type`, `Press`, `Screenshot`), as in [JavaScript](./javascript). ## Metrics, events and webhooks ```go package main import ( "context" "fmt" "io" "log" "net/http" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } hook, err := client.Webhooks.Create(ctx, withruntime.CreateWebhookOptions{URL: "https://example.com/hooks/runtime"}) if err != nil { log.Fatal(err) } secret := hook.Secret // shown once; keep it http.HandleFunc("/hooks/runtime", func(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) event, err := withruntime.VerifyWebhook(body, r.Header.Get("Runtime-Signature"), []string{secret}, 0) if err != nil { http.Error(w, "bad signature", http.StatusUnauthorized) return } fmt.Println(event.Type) w.WriteHeader(http.StatusNoContent) }) for event, err := range client.Events.All(ctx, &withruntime.EventListOptions{Type: "sandbox.stopped"}) { if err != nil { log.Fatal(err) } fmt.Println(event.CreatedAt, event.Type) } } ``` `sbx.Metrics(ctx, "15m")` returns the sandbox's measured CPU and memory, and `client.Otel.Create` pushes events and metrics to an OpenTelemetry endpoint. See [metrics and webhooks](./observability). ## An endpoint newer than the SDK `client.Request` calls any API path with the client's key, retries, idempotency keys and errors: ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { client, err := withruntime.New() if err != nil { log.Fatal(err) } var page map[string]any if err := client.Request(context.Background(), "GET", "/v1/volumes", nil, &page); err != nil { log.Fatal(err) } fmt.Println(page["data"]) } ``` ## Find sandboxes again ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } filter := &withruntime.ListOptions{Labels: map[string]string{"team": "search"}, State: []string{"running"}} for sbx, err := range client.Sandboxes.All(ctx, filter) { if err != nil { log.Fatal(err) } fmt.Println(sbx.ID(), sbx.State()) } } ``` `Sandboxes.List` returns one page (`Data`, `HasMore`, `Next`); `All` walks every page. ## Errors and retries ```go package main import ( "context" "errors" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } _, err = client.Sandboxes.Get(ctx, "00000000-0000-4000-8000-000000000000") var failure *withruntime.Error switch { case errors.Is(err, withruntime.ErrNotFound): fmt.Println("no such sandbox") case errors.As(err, &failure): fmt.Println(failure.Code, failure.Hint, failure.RequestID) } } ``` Every error is a `*withruntime.Error` with `Code`, `Message`, `Hint`, `RequestID`, `Status` and `Details`. `errors.Is` matches it against `ErrAuthentication`, `ErrPermissionDenied`, `ErrNotFound`, `ErrConflict`, `ErrInvalidRequest`, `ErrRateLimited`, `ErrServiceUnavailable` and `ErrConnection`. These are the same classes as in the JavaScript and Python SDKs. `no_capacity` matches `ErrServiceUnavailable`. `no_credit_running_limit`, and a `quota_exceeded` on what runs concurrently, match `ErrRateLimited`. A `quota_exceeded` on what is kept matches `ErrConflict`. Every write carries an idempotency key. The SDK makes it for you and keeps it across the client's own retries. Transport failures, and every response that says `retryable: true`, are retried after its `Retry-After`. Retries stop at the maximum number of retries. They use the same key, so a retry never makes two sandboxes or runs a command twice. A wait over 60 seconds is not slept on. The error is returned immediately, with `retryAfterMs`. A response that ran nothing (`guest_busy`, `busy`, `rate_limited`, `sandbox_starting`) is sent again until the call's deadline. Without a deadline, it is sent again for five minutes, past the maximum. `Retryable()` says whether trying the same call again may work. **A create, wake or restart waits for room.** When all four slots without credit are taken (`no_credit_running_limit`), the account is at its limit (`quota_exceeded`), or the region is full (`no_capacity`), `Sandboxes.Create` waits. It sends the same request again, for up to two minutes. A burst of CI jobs past the limit queues instead of failing. Set `withruntime.WithWaitForCapacity` on the client, or `WaitForCapacity` on one create, wake or restart. Zero fails immediately. A sandbox stays on its server. So a wake or restart refused with `no_capacity` is sent again with the same idempotency key until room frees. A fork waits the same way when no server has room for a copy; set `WaitForCapacity` on its `ForkOptions` to change the wait. ## Read-only keys and daily limits An owner, admin or developer can make a read-only key and set a daily spending limit on a key, on the [API keys](https://withruntime.com/account/keys) page. `client.Limits.Get(ctx)` reads both. Past the limit, a create, wake or extension returns an error whose code is `spending_limit_reached`. It is not retried. See [security](./security). ## Configuration ```go package main import ( "context" "fmt" "log" "os" "time" "withruntime.com/go" ) func main() { client, err := withruntime.New( withruntime.WithAPIKey(os.Getenv("MY_RUNTIME_KEY")), withruntime.WithTimeout(2*time.Minute), withruntime.WithMaxRetries(4), withruntime.WithWaitForCapacity(10*time.Minute), ) if err != nil { log.Fatal(err) } me, err := client.Me(context.Background()) if err != nil { log.Fatal(err) } fmt.Println(me.OrgID) } ``` - `RUNTIME_API_URL` points the client at another API origin, as `WithBaseURL` does. - Code inside a Runtime sandbox calls Runtime's API at `http://runtime.internal` ([Runtime's API from inside a sandbox](./sandbox-environment#runtime-s-api-from-inside-a-sandbox)). - The client is safe for concurrent use. Make one and share it: it keeps its connections open and holds at most 32 calls in flight (`WithMaxConnections`). - It honours `HTTPS_PROXY` and `NO_PROXY`. `WithHTTPClient` supplies your own `*http.Client`. - Money is integer microdollars: 1,000,000 is one US dollar. `client.Usage(ctx)` returns the account's balance as exact decimal strings. - `client.UsageExport(ctx, withruntime.UsageExportOptions{Since: ..., Until: ..., Cursor: ...})` reads one page of settled usage over a UTC range, with its server CSV. Follow `NextCursor` until it is nil. `FinalThrough` is the time before which no more charges will settle. A row's `Final` is true once its resource is deleted and can be charged nothing more ([export settled usage](./api#export-settled-usage)). ## Domains, public ports and private networking `client.Domains`, `Ports`, `Addresses` and `Tunnel` are account products. Domains and public ports name the sandbox they serve. `Tunnel` manages a WireGuard network; `sbx.OpenTunnel` and `sbx.PortForward` open authenticated TCP connections from your process into one sandbox. When you use `OpenTunnel` directly, read responses while you write requests. Each stream keeps at most 1 MiB of unread response data. If its reader falls behind that limit, reads return `tunnel_receive_overflow` after the buffered data. Other streams keep working. `PortForward` drains both directions for you. ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Get(ctx, "11111111-2222-4333-8444-555555555555") if err != nil { log.Fatal(err) } domain, err := client.Domains.Add(ctx, withruntime.AddDomainOptions{ Hostname: "app.example.com", SandboxID: sbx.ID(), Port: 3000, }) if err != nil { log.Fatal(err) } fmt.Println(domain.Records) // Publish these at your DNS provider, then call Domains.Verify. address, err := client.IPs.Reserve(ctx, 4) if err != nil { log.Fatal(err) } fmt.Println(address.Address, address.Funded, address.FundedUntil) forward, err := sbx.PortForward(ctx, 5432, "127.0.0.1:0") if err != nil { log.Fatal(err) } defer forward.Close() fmt.Println(forward.Addr()) // Connect your local database client here. } ``` `Ports.Open` returns `Connect`, the public address and port. `Networks.WireGuard.Create` creates the account's WireGuard network. `AddPeer` and `RotatePeer` return a configuration that holds a generated private key, shown once. Save `Config` even when `ConfigReady` is false. It is a draft. Fill in its gateway public key from `Networks.WireGuard.Get` before use. A request for a generated key is never retried automatically. To keep the private key entirely on your machine, supply `PublicKey`. Dedicated addresses and WireGuard networks expose `Funded`, `FundedUntil`, `RateMicros` and `RateUnit`. When credit runs out, traffic stops. The reservation is kept until you release it. `SSO.Get` reads sign-on and directory status, and the website address where an owner manages it. ## Backup and restore volumes `Volumes.Backup` requests a checked copy off the host. `Backups` lists copies. `GetBackup` and `DeleteBackup` read or remove one. `SetBackupPolicy` changes daily backups or retention. It can also back up the volume on a schedule of its own: `SetBackupPolicyOptions{Schedule: &withruntime.BackupSchedule{EveryMinutes: 60}, Keep: &withruntime.BackupKeep{Hourly: 24, Daily: 7}}` (or `Cron: "30 2 * * 1-5"`, in UTC). `NoSchedule: true` goes back to daily. Restore makes a new volume from a ready backup. ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } backup, err := client.Volumes.Backup(ctx, "11111111-2222-4333-8444-555555555555", nil) if err != nil { log.Fatal(err) } if backup.State != "ready" { fmt.Println("Backup is still running; poll Volumes.GetBackup:", backup.ID) return } volume, err := client.Volumes.Restore(ctx, backup.ID, &withruntime.CreateVolumeOptions{Name: "restored"}) if err != nil { log.Fatal(err) } fmt.Println(volume.ID, volume.State, volume.RestoredFrom) } ``` ## Permissions and file watching Pass `FileWriteOptions` to set permissions with the content. A pointer keeps mode `000` distinct from the default. Current guest images require only `write_file`. Older images require `exec` for the SDK's chmod fallback. `Files.Chmod` sets an existing file's permissions through the same exec grant. A supplied write key identifies the whole upload. Each mutation phase has its own stable key. A repeat of a completed upload returns without sending its chunks again. ```go package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Get(ctx, "11111111-2222-4333-8444-555555555555") if err != nil { log.Fatal(err) } mode := uint32(0o755) if err := sbx.Files.Write(ctx, "/workspace/run.sh", []byte("#!/bin/sh\necho ready\n"), &withruntime.FileWriteOptions{Mode: &mode}); err != nil { log.Fatal(err) } watch, err := sbx.Files.Watch(ctx, "/workspace", &withruntime.WatchOptions{Recursive: true, Exclude: []string{"node_modules"}}) if err != nil { log.Fatal(err) } defer watch.Stop(context.Background()) for batch, err := range watch.Events(ctx) { if err != nil { log.Fatal(err) } fmt.Println(batch.Kind, batch.Events, batch.Reason, watch.Cursor()) } } ``` `Events` reconnects on a continuation and keeps its cursor. A pause ends iteration without stopping the guest watch. After waking the sandbox, call `Events` again. `overflow` and `lost` notices mean you should rescan. Breaking iteration closes its HTTP response. `Stop` also stops the guest watch. `Files.Watches.List` and `Read` let you find watches and poll instead. ## Bucket mounts and recordings `sbx.Mounts.Add` mounts a customer bucket, using the name of a Runtime secret. `List` and `Remove` manage mounts. The sandbox never receives the bucket key. `sbx.Desktop.Recordings` has `Start`, `Get`, `List`, `Stop`, `Download` and `Delete`. Downloaded bytes are MP4. ## CI/CD (beta) `client.CI` reads CI/CD. It checks every push and pull request of a connected GitHub repository in sandboxes. `CI.Repositories` has `Connect`, `List` and `Disconnect`. Only a full key can connect one. `CI.Checks.List` and `Get`, `CI.Jobs.Get` (with the tail of the job's log), and `CI.Tests.List` read what ran. `CI.Releases.List` reads a release's steps as they were taken. Times are milliseconds since the epoch. Lists page like the others. An account outside the beta gets `product_in_beta` (403). ```go check package main import ( "context" "fmt" "log" "withruntime.com/go" ) func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } repository, err := client.CI.Repositories.Connect(ctx, "acme/web", nil) if err != nil { log.Fatal(err) } for check, err := range client.CI.Checks.All(ctx, &withruntime.CICheckListOptions{RepositoryID: repository.ID, State: []string{"failed"}}) { if err != nil { log.Fatal(err) } detail, err := client.CI.Checks.Get(ctx, check.ID) if err != nil { log.Fatal(err) } for _, job := range detail.Jobs { if job.State == "failed" { failed, err := client.CI.Jobs.Get(ctx, job.ID) if err != nil { log.Fatal(err) } fmt.Println(job.Name, failed.Log) // job.SandboxIDs may still be paused, to wake and look into. } } } } ```