The official Go SDK for MIOSA. Requires Go 1.21+. All methods accept a context.Context for cancellation and timeouts.
Install
go get github.com/Miosa-osa/miosa-go@v0.4.0 What’s new in v0.4.0
| Area | What shipped |
|---|---|
| Sandbox lifecycle | Sandboxes.Update(), Sandboxes.PreviewToken(), Sandboxes.Fork() |
| Tenant preview domain | Tenant.GetPreviewDomain(), SetPreviewDomain(), VerifyPreviewDomain(), DeletePreviewDomain() |
| Tenant branding | Tenant.GetBranding(), SetBranding(), DeleteBranding() |
| Webhooks | Webhooks.Create/List/Get/Delete/Test() |
| Files advanced | SandboxFiles.Tree(), WriteMany(), Watch() |
| Sandbox env | SandboxEnv.List/Set/Delete() |
| Processes | SandboxProcesses.Start/List/Get/Stop/Logs/Stream() |
| Templates + fork | SandboxTemplates.*, Sandboxes.Fork() |
| Usage / quotas | Usage.Get(), Quotas.Set/Get/Delete() |
| Sharing | SandboxShare.Create/List/Revoke() |
| Workspace members | WorkspaceMembers.*, WorkspaceInvites.* |
| Org invites | OrgInvites.* |
First request
package main
import (
"context"
"fmt"
miosa "github.com/Miosa-osa/miosa-go"
)
func main() {
ctx := context.Background()
client := miosa.NewClient("msk_live_...")
// Create a sandbox, run a command, delete it
sandbox, err := client.Sandboxes.Create(ctx, miosa.CreateSandboxInput{
Name: "quick-exec",
})
if err != nil {
panic(err)
}
result, err := client.Exec.Run(ctx, sandbox.ID, miosa.ExecParams{
Command: "python3 -c 'print(1 + 1)'",
})
if err != nil {
panic(err)
}
fmt.Println(result.Output) // "2\n"
_ = client.Sandboxes.Delete(ctx, sandbox.ID)
} Configure
| Option | Env var | Default | Description |
|---|---|---|---|
apiKey (positional) | MIOSA_API_KEY | - | Workspace key (msk_live_*) |
WithBaseURL(u) | - | https://api.miosa.ai/api/v1 | API endpoint |
WithTimeout(d) | - | 60s | Per-request timeout |
WithMaxRetries(n) | - | 3 | Automatic retries on 429 / 5xx |
WithHTTPClient(hc) | - | default http.Client | Custom HTTP client |
import (
"time"
miosa "github.com/Miosa-osa/miosa-go"
)
client := miosa.NewClient(
"msk_live_...",
miosa.WithBaseURL("https://api.miosa.ai/api/v1"),
miosa.WithTimeout(60 * time.Second),
miosa.WithMaxRetries(3),
) Authentication
Keys are passed as Authorization: Bearer <key>. The SDK validates the key format at construction time.
import "os"
client := miosa.NewClient(os.Getenv("MIOSA_API_KEY")) Error handling
Use errors.As to match typed errors:
import (
"errors"
"fmt"
miosa "github.com/Miosa-osa/miosa-go"
)
_, err := client.Computers.Get(ctx, "nonexistent-id")
if err != nil {
var notFound *miosa.NotFoundError
var rateLimit *miosa.RateLimitError
switch {
case errors.As(err, ¬Found):
fmt.Println("Computer not found")
case errors.As(err, &rateLimit):
fmt.Printf("Rate limited - retry after %.0fs\n", rateLimit.RetryAfter)
default:
fmt.Println("Error:", err)
}
} Streaming events
stream, err := computer.Events.Subscribe(ctx, miosa.EventSubscribeOptions{
Subscribe: []miosa.EventProducer{
miosa.ProducerWindow,
miosa.ProducerFile,
miosa.ProducerProcess,
},
Paths: []string{"/workspace"},
})
if err != nil {
panic(err)
}
defer stream.Close()
for ev := range stream.C {
fmt.Printf("[%s] %v\n", ev.Type, ev.Payload)
} The stream.C channel is closed when the connection closes or the context is cancelled.
Backend orchestration pattern
Use the SDK from your backend when you are building an agent product. The SDK owns the MIOSA resource lifecycle; your agent runtime receives the prompt and does the work inside the assigned sandbox or computer.
func StartAgentRun(ctx context.Context, client *miosa.Client, workspaceID, userID, prompt string) (*miosa.Sandbox, error) {
sandbox, err := client.Sandboxes.Create(ctx, miosa.CreateSandboxInput{
Name: "workspace-" + workspaceID,
ExternalWorkspaceID: workspaceID,
ExternalUserID: userID,
TimeoutSec: 3600,
IdempotencyKey: uuid.NewString(),
})
if err != nil {
return nil, err
}
run, err := client.AgentRuns.Run(ctx, miosa.CreateAgentRunInput{
SandboxID: sandbox.ID,
TargetKind: miosa.AgentRunTargetSandbox,
Provider: "claude-code",
Cwd: "/workspace",
Timeout: 1800,
Prompt: prompt,
Metadata: map[string]interface{}{
"workspace_id": workspaceID,
"user_id": userID,
},
})
if err != nil {
return nil, err
}
_ = run
return sandbox, nil
} For the full backend architecture, see Backend orchestration and Build an agent-company platform. For the
REST shape behind client.AgentRuns, see Agent Runs.
Phase 1-5 API Reference
Sandbox lifecycle (Phase 1)
client.Sandboxes - *SandboxesService
// Create - full input struct
sandbox, err := client.Sandboxes.Create(ctx, miosa.CreateSandboxInput{
Name: "my-sandbox",
Slug: "acme-backend",
ExternalUserID: "usr_123",
ExternalProjectID: "proj_456",
ExternalWorkspaceID: "ws_789",
Metadata: map[string]interface{}{"env": "staging"},
TimeoutSec: 300,
AlwaysOn: false,
IdempotencyKey: uuid.NewString(),
})
sandbox, err = client.Sandboxes.Get(ctx, sandboxID)
list, err := client.Sandboxes.List(ctx, miosa.SandboxListParams{State: "running"})
err = client.Sandboxes.Delete(ctx, sandboxID)
// Update mutable fields
updated, err := client.Sandboxes.Update(ctx, sandboxID, miosa.UpdateSandboxInput{
Name: "updated-name",
Slug: "new-slug",
TimeoutSec: 600,
AlwaysOn: true,
})
// Mint a short-lived preview token
token, err := client.Sandboxes.PreviewToken(ctx, sandboxID, miosa.PreviewTokenInput{
ExpiresIn: 3600,
Scope: "read",
})
// token.Token, token.URL, token.ExpiresAt
// Fork from a snapshot or running sandbox
forked, err := client.Sandboxes.Fork(ctx, sandboxID, miosa.ForkSandboxInput{
SnapshotID: "snap_abc",
Name: "forked-sandbox",
ExternalUserID: "usr_123",
}) | Field | Type | Description |
|---|---|---|
Name | string | Display name |
Slug | string | URL-safe identifier |
ExternalUserID | string | White-label user attribution |
ExternalProjectID | string | White-label project attribution |
ExternalWorkspaceID | string | White-label workspace attribution |
Metadata | map[string]interface{} | Arbitrary key-value pairs |
TimeoutSec | int | Lifetime in seconds |
AlwaysOn | bool | Stay alive past idle timeout |
IdempotencyKey | string | Deduplication key for retries |
Tenant preview domain (Phase 1)
client.Tenant - *TenantService
// Get current custom preview domain
info, err := client.Tenant.GetPreviewDomain(ctx)
// info.Domain, info.VerifiedAt, info.CnameTarget
// Set custom preview domain
updated, err := client.Tenant.SetPreviewDomain(ctx, "preview.acme.com")
// Trigger DNS verification
result, err := client.Tenant.VerifyPreviewDomain(ctx)
// result["verified"], result["target"], result["records"]
// Remove custom domain
err = client.Tenant.DeletePreviewDomain(ctx) Tenant branding (Phase 1)
client.Tenant - *TenantService
// Get current branding
branding, err := client.Tenant.GetBranding(ctx)
// Set branding
updated, err := client.Tenant.SetBranding(ctx, miosa.BrandingData{
ProductName: "Acme AI",
LogoURL: "https://cdn.acme.com/logo.png",
SupportURL: "https://acme.com/support",
SupportEmail: "help@acme.com",
PrimaryColor: "#ff6600",
BackgroundColor: "#ffffff",
})
// Reset to platform defaults
err = client.Tenant.DeleteBranding(ctx) Webhooks (Phase 1)
client.Webhooks - *WebhooksService
// Create a webhook
wh, err := client.Webhooks.Create(ctx, miosa.CreateWebhookInput{
URL: "https://example.com/webhooks/miosa",
Events: []string{"sandbox.created", "sandbox.ready", "sandbox.destroyed"},
})
list, err := client.Webhooks.List(ctx)
wh, err = client.Webhooks.Get(ctx, whID)
err = client.Webhooks.Delete(ctx, whID)
err = client.Webhooks.Test(ctx, whID) Supported events: sandbox.created, sandbox.ready, sandbox.destroyed, sandbox.error, computer.started, computer.stopped, computer.error
Files advanced (Phase 2)
client.SandboxFiles(sandboxID) - *SandboxFilesService
files := client.SandboxFiles(sandboxID)
// Recursive directory tree
tree, err := files.Tree(ctx, "/workspace", 5) // path, depth
// Batch file write
result, err := files.WriteMany(ctx, []miosa.WriteFileInput{
{Path: "/workspace/app.go", ContentBase64: base64.StdEncoding.EncodeToString([]byte("package main"))},
})
// Watch for file-system changes (SSE channel)
events, err := files.Watch(ctx)
if err != nil {
panic(err)
}
for ev := range events {
fmt.Println(ev.Type, ev.Data)
} Sandbox environment variables (Phase 2)
client.SandboxEnv(sandboxID) - *SandboxEnvService
env := client.SandboxEnv(sandboxID)
vars, err := env.List(ctx)
_, err = env.Set(ctx, []miosa.EnvVar{{Key: "DATABASE_URL", Value: "postgres://..."}})
err = env.Delete(ctx, "DATABASE_URL") Processes (Phase 2)
client.SandboxProcesses(sandboxID) - *SandboxProcessesService
procs := client.SandboxProcesses(sandboxID)
// Start a long-running background process
proc, err := procs.Start(ctx, miosa.StartProcessInput{
Command: "npm run dev",
Env: map[string]string{"NODE_ENV": "development"},
Name: "dev-server",
})
list, err := procs.List(ctx)
one, err := procs.Get(ctx, proc.PID)
err = procs.Stop(ctx, proc.PID)
logs, err := procs.Logs(ctx, proc.PID, 100)
// Streaming process output (SSE channel)
stream, err := procs.Stream(ctx, proc.PID)
for ev := range stream {
fmt.Print(ev.Data)
} Usage and quotas (Phase 3)
client.Usage - client.Quotas
// Usage rollup
report, err := client.Usage.Get(ctx, miosa.UsageParams{
ExternalUserID: "usr_123",
GroupBy: "external_user_id",
Period: "30d",
})
// Set per-user quotas
quota, err := client.Quotas.Set(ctx, "usr_123", miosa.QuotaInput{
MaxSandboxes: 5,
MaxConcurrent: 2,
MaxStorageGB: 10,
MaxCreditCents: 10000,
})
quota, err = client.Quotas.Get(ctx, "usr_123")
err = client.Quotas.Delete(ctx, "usr_123") Audit log (Phase 3)
client.AuditLog
var after string
for {
page, err := client.AuditLog.List(ctx, miosa.AuditLogListParams{
After: after,
Limit: 50,
})
if err != nil {
break
}
for _, entry := range page.Items {
fmt.Println(entry.Action, entry.CreatedAt)
}
if page.NextCursor == "" {
break
}
after = page.NextCursor
} Sharing (Phase 4)
client.SandboxShare(sandboxID) - *SandboxShareService
share := client.SandboxShare(sandboxID)
// Create a public share URL (no API key required to access)
expiresIn := 3600
data, err := share.Create(ctx, miosa.CreateShareInput{
ExpiresIn: &expiresIn,
Scope: "read",
})
// data.ShareID, data.ShareURL, data.ExpiresAt
shares, err := share.List(ctx)
err = share.Revoke(ctx, shareID) White-label integrator flow
package main
import (
"context"
"fmt"
"os"
"github.com/google/uuid"
miosa "github.com/Miosa-osa/miosa-go"
)
func main() {
ctx := context.Background()
client := miosa.NewClient(os.Getenv("MIOSA_API_KEY"))
// 1. Configure tenant branding once
client.Tenant.SetBranding(ctx, miosa.BrandingData{
ProductName: "Acme AI",
LogoURL: "https://cdn.acme.com/logo.png",
PrimaryColor: "#ff6600",
})
// 2. Set and verify a custom preview domain
client.Tenant.SetPreviewDomain(ctx, "preview.acme.com")
client.Tenant.VerifyPreviewDomain(ctx)
// 3. Register webhook
client.Webhooks.Create(ctx, miosa.CreateWebhookInput{
URL: "https://api.acme.com/webhooks/miosa",
Events: []string{"sandbox.created", "sandbox.ready", "sandbox.destroyed"},
})
// 4. Set per-user quotas
client.Quotas.Set(ctx, "usr_dr_smith", miosa.QuotaInput{
MaxSandboxes: 3,
MaxConcurrent: 1,
})
// 5. Create a sandbox attributed to a user + project
sandbox, err := client.Sandboxes.Create(ctx, miosa.CreateSandboxInput{
Name: "smile-dental",
ExternalUserID: "usr_dr_smith",
ExternalProjectID: "proj_landing_page",
ExternalWorkspaceID: "ws_smile_dental",
Metadata: map[string]interface{}{"plan": "pro"},
IdempotencyKey: uuid.NewString(),
})
if err != nil {
panic(err)
}
// 6. Write files
import64 := func(s string) string {
return base64.StdEncoding.EncodeToString([]byte(s))
}
client.SandboxFiles(sandbox.ID).WriteMany(ctx, []miosa.WriteFileInput{
{Path: "/workspace/index.html", ContentBase64: import64("<h1>Hello</h1>")},
})
// 7. Mint a preview token
expiresIn := 3600
token, _ := client.Sandboxes.PreviewToken(ctx, sandbox.ID, miosa.PreviewTokenInput{
ExpiresIn: expiresIn,
Scope: "read",
})
fmt.Println("Preview URL:", token.URL)
// 8. Create a public share link
shareExpiresIn := 86400
share, _ := client.SandboxShare(sandbox.ID).Create(ctx, miosa.CreateShareInput{
ExpiresIn: &shareExpiresIn,
Scope: "read",
})
fmt.Println("Public URL:", share.ShareURL)
// 9. Query usage
report, _ := client.Usage.Get(ctx, miosa.UsageParams{
ExternalUserID: "usr_dr_smith",
Period: "30d",
})
fmt.Println(report)
// 10. Clean up
client.Sandboxes.Delete(ctx, sandbox.ID)
} Common patterns
Context-based timeouts
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()
result, err := client.Exec.Run(ctx, computerID, miosa.ExecParams{
Command: "npm run build",
Timeout: 80,
}) Idempotency key
import "github.com/google/uuid"
sandbox, err := client.Sandboxes.Create(ctx, miosa.CreateSandboxInput{
Name: "my-sandbox",
IdempotencyKey: uuid.NewString(),
}) Custom base URL (self-hosted)
client := miosa.NewClient(
"msk_live_...",
miosa.WithBaseURL("https://api.your-domain.com/api/v1"),
) Resource coverage
| Resource group | Available |
|---|---|
| Sandboxes (create, get, list, delete, update, previewToken, fork) | ✓ |
| Sandbox files (tree, writeMany, watch) | ✓ |
| Sandbox env, processes, share | ✓ |
| Computers (CRUD + lifecycle) | ✓ |
| Computer sub-resources (terminal, env, ports, volumes, logs, auto-stop, metrics) | ✓ |
| Desktop | ✓ |
| Files (upload, download, list, delete) | ✓ |
| Exec | ✓ |
| Deployments | ✓ |
| Databases (managed Postgres) | ✓ |
| Storage (object storage) | ✓ |
| Volumes | ✓ |
| Custom domains | ✓ |
| Functions / cron jobs / health checks / webhooks | ✓ |
| Sandbox templates | ✓ |
| API keys | ✓ |
| Tenant (plan, preview domain, branding) | ✓ |
| Regions / settings | ✓ |
| Dashboard / analytics / audit log / usage / quotas | ✓ |
| Channels / integrations | ✓ |
| External keys | ✓ |
| MCP | ✓ |
| Models / completions / embeddings | ✓ |
| Command center / builder sessions / community | ✓ |
| OpenComputers (hosts, jobs, files, tunnels, agents, clusters) | ✓ |
| Workspace members + invites | ✓ |
| Org invites | ✓ |
| Admin | ✓ |
| Snapshots (standalone) | ✓ |
| Credits | ✓ |