openapi: 3.1.0 info: title: Superserve API version: 0.1.0 description: | Superserve provides sandbox infrastructure to run AI Agents in the cloud. Powered by Firecracker MicroVMs. ## Sandbox lifecycle ``` starting --> active <--> idle --> deleted ``` | Endpoint | What it does | |----------|-------------| | `POST /sandboxes` | Create a new sandbox | | `PATCH /sandboxes/:id` | Partially update a running sandbox (e.g. network rules) | | `POST /sandboxes/:id/pause` | Snapshot full state, suspend the VM | | `POST /sandboxes/:id/resume` | Restore from snapshot, continue where it left off | | `DELETE /sandboxes/:id` | Delete sandbox and all resources | ## Sandbox environment Each sandbox runs Ubuntu 24.04 with 1 vCPU, 1 GB RAM, 4 GB disk. Pre-installed: Python 3.12, Node.js 22, npm, git, curl, build-essential. contact: name: Superserve Team license: name: Proprietary servers: - url: https://api-staging.superserve.ai description: Staging - url: https://api.superserve.ai description: Production - url: http://localhost:8080 description: Local development paths: /health: get: operationId: health tags: [System] summary: Health check responses: "200": description: Service is healthy content: application/json: schema: type: object properties: status: type: string example: ok version: type: string example: "0.1.0" /sandboxes: get: operationId: listSandboxes tags: [Sandboxes] summary: List all sandboxes security: - apiKey: [] responses: "200": description: List of sandboxes belonging to the authenticated team content: application/json: schema: type: array items: $ref: "#/components/schemas/SandboxResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" post: operationId: createSandbox tags: [Sandboxes] summary: Create a new sandbox description: | Creates a sandbox and returns immediately with `status: starting`. The VM boots asynchronously. Poll `GET /sandboxes/{id}` until `status` is `active` before running commands. security: - apiKey: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CreateSandboxRequest" responses: "201": description: Sandbox created content: application/json: schema: $ref: "#/components/schemas/SandboxResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /sandboxes/{sandbox_id}: parameters: - $ref: "#/components/parameters/SandboxId" get: operationId: getSandbox tags: [Sandboxes] summary: Get a sandbox by ID security: - apiKey: [] responses: "200": description: Sandbox details content: application/json: schema: $ref: "#/components/schemas/SandboxResponse" "404": $ref: "#/components/responses/NotFound" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" delete: operationId: deleteSandbox tags: [Sandboxes] summary: Delete a sandbox security: - apiKey: [] responses: "204": description: Sandbox deleted "404": $ref: "#/components/responses/NotFound" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" patch: operationId: patchSandbox tags: [Sandboxes] summary: Partially update a running sandbox description: | Applies a partial update to a running sandbox. Each top-level field in the request body is optional; only fields that are present are applied. Omitted top-level fields are left unchanged. Nested objects are full replacements when present — to clear a list, send it as an empty array. At least one top-level field must be present, otherwise the request is rejected with `400`. Unknown top-level fields are also rejected with `400` so typos surface as errors instead of silent no-ops. ## Currently patchable fields - `network` — replaces the egress allow/deny rules. The sandbox must be in the `active` state; patching a paused or idle sandbox returns `409`. Rules take effect immediately and are persisted so they survive a future pause/resume cycle. security: - apiKey: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/SandboxPatch" responses: "204": description: Patch applied "400": $ref: "#/components/responses/BadRequest" "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/Conflict" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /sandboxes/{sandbox_id}/pause: parameters: - $ref: "#/components/parameters/SandboxId" post: operationId: pauseSandbox tags: [Sandboxes] summary: Pause a running sandbox description: | Snapshots the sandbox's full state (memory + disk), suspends the VM, and transitions to `idle`. Resume it later to continue exactly where it left off. security: - apiKey: [] responses: "200": description: Sandbox is now idle content: application/json: schema: $ref: "#/components/schemas/SandboxResponse" "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/Conflict" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /sandboxes/{sandbox_id}/resume: parameters: - $ref: "#/components/parameters/SandboxId" post: operationId: resumeSandbox tags: [Sandboxes] summary: Resume a paused sandbox description: | Restores the sandbox from its paused snapshot. Transitions back to `active` with all state intact — same memory, same processes, same files. security: - apiKey: [] responses: "200": description: Sandbox is now active content: application/json: schema: $ref: "#/components/schemas/SandboxResponse" "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/Conflict" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /sandboxes/{sandbox_id}/exec: parameters: - $ref: "#/components/parameters/SandboxId" post: operationId: execCommand tags: [Exec] summary: Execute a command inside a sandbox description: | Runs a command inside the sandbox and waits for it to finish, returning stdout, stderr, and exit code. Idle sandboxes are automatically resumed before execution. security: - apiKey: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ExecRequest" responses: "200": description: Command executed content: application/json: schema: $ref: "#/components/schemas/ExecResult" "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/Conflict" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /sandboxes/{sandbox_id}/exec/stream: parameters: - $ref: "#/components/parameters/SandboxId" post: operationId: execCommandStream tags: [Exec] summary: Execute a command and stream output via SSE description: | Runs a command and streams stdout/stderr chunks as Server-Sent Events. Each event is a JSON payload. The final event includes `exit_code` and `finished: true`. Idle sandboxes are automatically resumed before execution. security: - apiKey: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ExecRequest" responses: "200": description: > SSE stream. Each line starting with `data: ` contains a JSON `ExecStreamEvent`. The final event has `finished: true`. content: text/event-stream: schema: $ref: "#/components/schemas/ExecStreamEvent" "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/Conflict" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /sandboxes/{sandbox_id}/files/{path}: parameters: - $ref: "#/components/parameters/SandboxId" - name: path in: path required: true schema: type: string description: File path inside the sandbox (without leading slash). put: operationId: uploadFile tags: [Files] summary: Upload a file into a sandbox description: Idle sandboxes are automatically resumed before the upload. security: - apiKey: [] requestBody: required: true content: application/octet-stream: schema: type: string format: binary responses: "200": description: File uploaded content: application/json: schema: type: object properties: path: type: string size: type: integer format: int64 "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/Conflict" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" get: operationId: downloadFile tags: [Files] summary: Download a file from a sandbox description: Idle sandboxes are automatically resumed before the download. security: - apiKey: [] responses: "200": description: File content content: application/octet-stream: schema: type: string format: binary "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/Conflict" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" components: securitySchemes: apiKey: type: apiKey in: header name: X-API-Key parameters: SandboxId: name: sandbox_id in: path required: true schema: type: string format: uuid description: The unique identifier of the sandbox. schemas: CreateSandboxRequest: type: object required: [name] properties: name: type: string minLength: 1 maxLength: 64 description: Human-readable name for the sandbox. from_snapshot: type: string format: uuid description: > If provided, boot the sandbox from this snapshot instead of creating a fresh VM. The snapshot must belong to the same team. network: $ref: "#/components/schemas/NetworkConfig" SandboxResponse: type: object properties: id: type: string format: uuid name: type: string status: type: string enum: [starting, active, pausing, idle, deleted] vcpu_count: type: integer memory_mib: type: integer ip_address: type: string description: IP address of the running VM (present when status is active). snapshot_id: type: string format: uuid description: ID of the latest snapshot (present after a pause). created_at: type: string format: date-time ExecStreamEvent: type: object description: A single SSE event payload emitted by the exec/stream endpoint. properties: timestamp: type: string format: date-time stdout: type: string description: Stdout chunk (present when the process wrote to stdout). stderr: type: string description: Stderr chunk (present when the process wrote to stderr). exit_code: type: integer description: Process exit code (present on the final event). finished: type: boolean description: True on the final event. error: type: string description: Error message if the exec itself failed (present on the final event on error). ExecRequest: type: object required: [command] properties: command: type: string minLength: 1 description: Command to execute. Wrapped in `/bin/sh -c` unless `args` is provided. args: type: array items: type: string description: If provided, `command` is used as the binary and `args` as arguments (no shell wrapping). env: type: object additionalProperties: type: string description: Environment variables for the command. working_dir: type: string description: Working directory (default /home/user). timeout_s: type: integer default: 30 description: Timeout in seconds. ExecResult: type: object properties: stdout: type: string stderr: type: string exit_code: type: integer NetworkConfig: type: object description: > Egress network rules for a sandbox. `allow_out` accepts CIDRs (e.g. `8.8.8.8/32`) and domain names (e.g. `api.openai.com`, `*.github.com`). `deny_out` accepts CIDRs only. Private ranges (10/8, 172.16/12, 192.168/16, 127/8, 169.254/16) are always blocked regardless of rules. properties: allow_out: type: array items: type: string description: CIDRs or domains to allow. example: ["api.openai.com", "*.github.com", "8.8.8.8/32"] deny_out: type: array items: type: string description: CIDRs to deny. Use `0.0.0.0/0` to block all traffic not in `allow_out`. example: ["0.0.0.0/0"] SandboxPatch: type: object description: | Partial update body for `PATCH /sandboxes/{sandbox_id}`. Each top-level field is optional; only fields that are present are applied. Omitted fields are left unchanged. Nested objects are full replacements when present — to clear a list, send it as an empty array. At least one top-level field must be set, otherwise the request is rejected with `400`. Unknown fields are also rejected with `400`. properties: network: allOf: - $ref: "#/components/schemas/NetworkConfig" description: | Replace the sandbox's egress rules. The sandbox must be in the `active` state. The provided `allow_out` and `deny_out` lists fully replace whatever was previously configured. example: network: allow_out: ["api.openai.com", "*.github.com"] deny_out: ["0.0.0.0/0"] Error: type: object properties: error: type: object properties: code: type: string message: type: string responses: BadRequest: description: Invalid request content: application/json: schema: $ref: "#/components/schemas/Error" Unauthorized: description: Missing or invalid API key content: application/json: schema: $ref: "#/components/schemas/Error" NotFound: description: Resource not found content: application/json: schema: $ref: "#/components/schemas/Error" Conflict: description: Sandbox is not in a valid state for this operation content: application/json: schema: $ref: "#/components/schemas/Error" InternalError: description: Internal server error content: application/json: schema: $ref: "#/components/schemas/Error"