Files
python-sdk/api/openapi.yaml
pptx704 4924237a23
All checks were successful
ci/woodpecker/push/unit Pipeline was successful
v0.2.0 (#14)
Co-authored-by: Tasnim Kabir Sadik <tksadik92@gmail.com>
Reviewed-on: #14
Co-authored-by: pptx704 <rafeed@omukk.dev>
Co-committed-by: pptx704 <rafeed@omukk.dev>
2026-05-24 05:02:08 +00:00

4494 lines
122 KiB
YAML

openapi: "3.1.0"
info:
title: Wrenn API
description: AI agent execution platform API.
version: "0.2.0"
servers:
- url: http://localhost:8080
description: Local development
security: []
paths:
/v1/auth/signup:
post:
summary: Create a new account
operationId: signup
tags: [auth]
description: |
Creates an inactive user account and sends an activation email.
The user must activate their account within 30 minutes.
Does not return a JWT — the user must activate first, then sign in.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SignupRequest"
responses:
"201":
description: Account created, activation email sent
content:
application/json:
schema:
$ref: "#/components/schemas/SignupResponse"
"400":
description: Invalid request (bad email, short password)
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Email already registered or signup cooldown active
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/auth/activate:
post:
summary: Activate account via email token
operationId: activate
tags: [auth]
description: |
Consumes the activation token sent via email and activates the user account.
Creates a default team and sets a session cookie to log the user in.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [token]
properties:
token:
type: string
responses:
"200":
description: Account activated, session cookie set
content:
application/json:
schema:
$ref: "#/components/schemas/SessionResponse"
"400":
description: Invalid or expired token
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/auth/logout:
post:
summary: Revoke the current session
operationId: logout
tags: [auth]
security:
- sessionAuth: []
responses:
"204":
description: Session revoked; cookies cleared
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/v1/auth/logout-all:
post:
summary: Revoke every session for the current user
operationId: logoutAll
tags: [auth]
description: |
Revokes every active session for the calling user across all devices,
including the caller's own. Returns 204 and clears cookies on the
response. Triggered automatically by password change, password add,
and password reset.
security:
- sessionAuth: []
responses:
"204":
description: All sessions revoked
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/v1/me/sessions:
get:
summary: List the caller's active sessions
operationId: listSessions
tags: [me]
security:
- sessionAuth: []
responses:
"200":
description: Sessions list
content:
application/json:
schema:
type: object
properties:
sessions:
type: array
items:
type: object
properties:
id:
type: string
user_agent:
type: string
ip_address:
type: string
created_at:
type: string
format: date-time
last_seen_at:
type: string
format: date-time
expires_at:
type: string
format: date-time
current:
type: boolean
"401":
$ref: "#/components/responses/Unauthorized"
/v1/me/sessions/{id}:
delete:
summary: Revoke a single session
operationId: revokeSession
tags: [me]
security:
- sessionAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"204":
description: Session revoked
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/v1/auth/switch-team:
post:
summary: Switch active team
operationId: switchTeam
tags: [auth]
security:
- sessionAuth: []
description: |
Rotates the session SID and updates its team scope. The user must be a
member of the target team (verified from DB). The new wrenn_sid and
wrenn_csrf cookies are set on the response.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [team_id]
properties:
team_id:
type: string
responses:
"200":
description: New session issued for the target team; cookies refreshed
content:
application/json:
schema:
$ref: "#/components/schemas/SessionResponse"
"403":
description: Not a member of this team
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"404":
description: Team not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/auth/login:
post:
summary: Log in with email and password
operationId: login
tags: [auth]
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/LoginRequest"
responses:
"200":
description: Login successful
content:
application/json:
schema:
$ref: "#/components/schemas/SessionResponse"
"401":
description: Invalid credentials
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/auth/oauth/{provider}:
parameters:
- name: provider
in: path
required: true
schema:
type: string
enum: [github]
description: OAuth provider name
get:
summary: Start OAuth login flow
operationId: oauthRedirect
tags: [auth]
description: |
Redirects the user to the OAuth provider's authorization page.
Sets a short-lived CSRF state cookie for validation on callback.
responses:
"302":
description: Redirect to provider authorization URL
"404":
description: Provider not found or not configured
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/auth/oauth/{provider}/callback:
parameters:
- name: provider
in: path
required: true
schema:
type: string
enum: [github]
description: OAuth provider name
get:
summary: OAuth callback
operationId: oauthCallback
tags: [auth]
description: |
Handles the OAuth provider's callback after user authorization.
Exchanges the authorization code for a user profile, creates or
logs in the user, sets the wrenn_sid + wrenn_csrf cookies, and
redirects to the SPA callback page.
**On success:** redirects to `{OAUTH_REDIRECT_URL}/auth/{provider}/callback` (no tokens in URL).
**On error:** redirects to `{OAUTH_REDIRECT_URL}/auth/{provider}/callback?error=...`
Possible error codes: `access_denied`, `invalid_state`, `missing_code`,
`exchange_failed`, `email_taken`, `internal_error`.
parameters:
- name: code
in: query
schema:
type: string
description: Authorization code from the OAuth provider
- name: state
in: query
schema:
type: string
description: CSRF state parameter (must match the cookie)
responses:
"302":
description: Redirect to frontend with token or error
/v1/me:
get:
summary: Get current user profile
operationId: getMe
tags: [account]
security:
- sessionAuth: []
responses:
"200":
description: User profile
content:
application/json:
schema:
$ref: "#/components/schemas/MeResponse"
patch:
summary: Update display name
operationId: updateName
tags: [account]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name:
type: string
minLength: 1
maxLength: 100
responses:
"204":
description: Name updated; session caches refreshed
"400":
description: Invalid name
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
delete:
summary: Delete current account
operationId: deleteAccount
tags: [account]
security:
- sessionAuth: []
description: |
Soft-deletes the account (sets status=deleted, deleted_at=now).
The account is permanently removed after 15 days. Blocked if the user
owns any team that has other members.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [confirmation]
properties:
confirmation:
type: string
description: Must match the user's email address (case-insensitive)
responses:
"204":
description: Account scheduled for deletion
"400":
description: Confirmation does not match email
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: User owns teams with other members
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/me/password:
post:
summary: Change or add password
operationId: changePassword
tags: [account]
security:
- sessionAuth: []
description: |
For users with an existing password: requires `current_password` and `new_password`.
For OAuth-only users adding a password: requires `new_password` and `confirm_password`.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ChangePasswordRequest"
responses:
"204":
description: Password updated
"400":
description: Invalid request (short password, mismatch, etc.)
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"401":
description: Current password is incorrect
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/me/password/reset:
post:
summary: Request a password reset email
operationId: requestPasswordReset
tags: [account]
description: |
Sends a password reset link to the given email. Always returns 200
regardless of whether the email exists, to prevent account enumeration.
The reset token expires in 15 minutes.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email]
properties:
email:
type: string
format: email
responses:
"204":
description: Request accepted (email sent if account exists)
/v1/me/password/reset/confirm:
post:
summary: Confirm password reset
operationId: confirmPasswordReset
tags: [account]
description: |
Consumes a password reset token and sets a new password. The token is
single-use and expires after 15 minutes.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [token, new_password]
properties:
token:
type: string
description: Raw reset token from the email link
new_password:
type: string
minLength: 8
responses:
"204":
description: Password reset successful
"400":
description: Invalid or expired token, or password too short
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/me/providers/{provider}/connect:
parameters:
- name: provider
in: path
required: true
schema:
type: string
enum: [github]
description: OAuth provider name
get:
summary: Initiate OAuth provider link
operationId: connectProvider
tags: [account]
security:
- sessionAuth: []
description: |
Sets OAuth state and link cookies, then returns the provider's
authorization URL. The frontend navigates to this URL to start the
OAuth flow. On callback, the provider is linked to the current account
(not a new registration).
responses:
"200":
description: Authorization URL
content:
application/json:
schema:
type: object
properties:
auth_url:
type: string
format: uri
"404":
description: Provider not found or not configured
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/me/providers/{provider}:
parameters:
- name: provider
in: path
required: true
schema:
type: string
enum: [github]
description: OAuth provider name
delete:
summary: Disconnect an OAuth provider
operationId: disconnectProvider
tags: [account]
security:
- sessionAuth: []
description: |
Unlinks the OAuth provider from the current account. Blocked if this
is the user's only login method (no password and no other providers).
responses:
"204":
description: Provider disconnected
"400":
description: Cannot disconnect last login method
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"404":
description: Provider not connected
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/api-keys:
post:
summary: Create an API key
operationId: createAPIKey
tags: [api-keys]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateAPIKeyRequest"
responses:
"201":
description: API key created (plaintext key only shown once)
content:
application/json:
schema:
$ref: "#/components/schemas/APIKeyResponse"
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
get:
summary: List API keys for your team
operationId: listAPIKeys
tags: [api-keys]
security:
- sessionAuth: []
responses:
"200":
description: List of API keys (plaintext keys are never returned)
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/APIKeyResponse"
/v1/api-keys/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
delete:
summary: Delete an API key
operationId: deleteAPIKey
tags: [api-keys]
security:
- sessionAuth: []
responses:
"204":
description: API key deleted
/v1/users/search:
get:
summary: Search users by email prefix
operationId: searchUsers
tags: [users]
security:
- sessionAuth: []
description: |
Returns up to 10 users whose email starts with the given prefix.
The prefix must contain "@". Intended for the add-member UI autocomplete.
parameters:
- name: email
in: query
required: true
schema:
type: string
description: Email prefix (must contain "@", e.g. "alice@")
responses:
"200":
description: Matching users
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/UserSearchResult"
"400":
description: Prefix does not contain "@"
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/teams:
get:
summary: List teams for the authenticated user
operationId: listTeams
tags: [teams]
security:
- sessionAuth: []
responses:
"200":
description: Teams the user belongs to, each with their role
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/TeamWithRole"
post:
summary: Create a new team
operationId: createTeam
tags: [teams]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name:
type: string
description: 1-128 chars; A-Z a-z 0-9 space _
responses:
"201":
description: Team created (caller is owner)
content:
application/json:
schema:
$ref: "#/components/schemas/TeamWithRole"
"400":
description: Invalid team name
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/teams/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
description: Team ID (must match the JWT's team_id)
get:
summary: Get team info and member list
operationId: getTeam
tags: [teams]
security:
- sessionAuth: []
responses:
"200":
description: Team details with members
content:
application/json:
schema:
$ref: "#/components/schemas/TeamDetail"
"403":
description: JWT team does not match requested team
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"404":
description: Team not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
patch:
summary: Rename the team
operationId: renameTeam
tags: [teams]
security:
- sessionAuth: []
description: Admin or owner role required (verified from DB).
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name:
type: string
responses:
"204":
description: Renamed
"400":
description: Invalid team name
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"403":
description: Insufficient role
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
delete:
summary: Delete the team
operationId: deleteTeam
tags: [teams]
security:
- sessionAuth: []
description: |
Owner only. Soft-deletes the team and destroys all running/paused/starting
capsules. All DB records are preserved. The team slug is permanently reserved.
responses:
"204":
description: Team deleted
"403":
description: Caller is not the owner
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/teams/{id}/members:
parameters:
- name: id
in: path
required: true
schema:
type: string
get:
summary: List team members
operationId: listTeamMembers
tags: [teams]
security:
- sessionAuth: []
responses:
"200":
description: Members with roles
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/TeamMember"
post:
summary: Add a member by email
operationId: addTeamMember
tags: [teams]
security:
- sessionAuth: []
description: Admin or owner role required. User is added instantly as a member.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email]
properties:
email:
type: string
format: email
responses:
"201":
description: Member added
content:
application/json:
schema:
$ref: "#/components/schemas/TeamMember"
"403":
description: Insufficient role
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"404":
description: No account with that email
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"400":
description: User is already a member
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/teams/{id}/members/{uid}:
parameters:
- name: id
in: path
required: true
schema:
type: string
- name: uid
in: path
required: true
schema:
type: string
description: Target user ID
patch:
summary: Update member role
operationId: updateMemberRole
tags: [teams]
security:
- sessionAuth: []
description: |
Admin or owner required. Valid target roles: admin, member.
The owner's role cannot be changed.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [role]
properties:
role:
type: string
enum: [admin, member]
responses:
"204":
description: Role updated
"403":
description: Insufficient role or attempt to modify owner
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"404":
description: User is not a member
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
delete:
summary: Remove a member
operationId: removeTeamMember
tags: [teams]
security:
- sessionAuth: []
description: Admin or owner required. Owner cannot be removed.
responses:
"204":
description: Member removed
"403":
description: Insufficient role or attempt to remove owner
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"404":
description: User is not a member
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/teams/{id}/leave:
parameters:
- name: id
in: path
required: true
schema:
type: string
post:
summary: Leave the team
operationId: leaveTeam
tags: [teams]
security:
- sessionAuth: []
description: The owner cannot leave; they must delete the team instead.
responses:
"204":
description: Left the team
"403":
description: Owner cannot leave
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules:
post:
summary: Create a capsule
operationId: createCapsule
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateCapsuleRequest"
responses:
"202":
description: Capsule creation initiated (status will be "starting")
content:
application/json:
schema:
$ref: "#/components/schemas/Capsule"
"502":
description: Host agent error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
get:
summary: List capsules for your team
operationId: listCapsules
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
responses:
"200":
description: List of capsules
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Capsule"
/v1/capsules/stats:
get:
summary: Get capsule usage stats for your team
operationId: getCapsuleStats
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
parameters:
- name: range
in: query
required: false
schema:
type: string
enum: [5m, 1h, 6h, 24h, 30d]
default: 1h
description: Time window for the time-series data.
responses:
"200":
description: Capsule stats for the team
content:
application/json:
schema:
$ref: "#/components/schemas/CapsuleStats"
"400":
$ref: "#/components/responses/BadRequest"
/v1/capsules/usage:
get:
summary: Get daily CPU and RAM usage for your team
operationId: getCapsuleUsage
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
parameters:
- name: from
in: query
required: false
schema:
type: string
format: date
description: Start date (YYYY-MM-DD). Defaults to 30 days ago.
- name: to
in: query
required: false
schema:
type: string
format: date
description: End date (YYYY-MM-DD). Defaults to today.
responses:
"200":
description: Daily usage data for the team
content:
application/json:
schema:
$ref: "#/components/schemas/UsageResponse"
"400":
$ref: "#/components/responses/BadRequest"
/v1/capsules/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
get:
summary: Get capsule details
operationId: getCapsule
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
responses:
"200":
description: Capsule details
content:
application/json:
schema:
$ref: "#/components/schemas/Capsule"
"404":
description: Capsule not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
delete:
summary: Destroy a capsule
operationId: destroyCapsule
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
responses:
"202":
description: Capsule destruction initiated
/v1/capsules/{id}/exec:
parameters:
- name: id
in: path
required: true
schema:
type: string
post:
summary: Execute a command
operationId: execCommand
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ExecRequest"
responses:
"200":
description: Command output (foreground exec)
content:
application/json:
schema:
$ref: "#/components/schemas/ExecResponse"
"202":
description: Background process started
content:
application/json:
schema:
$ref: "#/components/schemas/BackgroundExecResponse"
"404":
description: Capsule not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Capsule not running
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/processes:
parameters:
- name: id
in: path
required: true
schema:
type: string
get:
summary: List running processes
operationId: listProcesses
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
description: |
Returns all running processes inside the capsule, including background
processes and any processes started by templates or init scripts.
responses:
"200":
description: Process list
content:
application/json:
schema:
$ref: "#/components/schemas/ProcessListResponse"
"404":
description: Capsule not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Capsule not running
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/processes/{selector}:
parameters:
- name: id
in: path
required: true
schema:
type: string
- name: selector
in: path
required: true
description: Process PID (numeric) or tag (string)
schema:
type: string
delete:
summary: Kill a process
operationId: killProcess
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
parameters:
- name: signal
in: query
required: false
description: Signal to send (SIGKILL or SIGTERM, default SIGKILL)
schema:
type: string
enum: [SIGKILL, SIGTERM]
default: SIGKILL
responses:
"204":
description: Process killed
"404":
description: Capsule or process not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Capsule not running
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/processes/{selector}/stream:
parameters:
- name: id
in: path
required: true
schema:
type: string
- name: selector
in: path
required: true
description: Process PID (numeric) or tag (string)
schema:
type: string
get:
summary: Stream process output via WebSocket
operationId: connectProcess
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
description: |
Opens a WebSocket connection to stream stdout/stderr from a running
background process. The selector can be a numeric PID or a string tag.
Server sends JSON messages:
- `{"type": "start", "pid": 42}` — connected to process
- `{"type": "stdout", "data": "..."}` — stdout output
- `{"type": "stderr", "data": "..."}` — stderr output
- `{"type": "exit", "exit_code": 0}` — process exited
- `{"type": "error", "data": "..."}` — error message
responses:
"101":
description: WebSocket upgrade
/v1/capsules/{id}/ping:
parameters:
- name: id
in: path
required: true
schema:
type: string
post:
summary: Reset capsule inactivity timer
operationId: pingCapsule
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
description: |
Resets the last_active_at timestamp for a running capsule, preventing
the auto-pause TTL from expiring. Use this as a keepalive for capsules
that are idle but should remain running.
responses:
"204":
description: Ping acknowledged, inactivity timer reset
"404":
description: Capsule not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Capsule not running
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/metrics:
parameters:
- name: id
in: path
required: true
schema:
type: string
get:
summary: Get per-capsule resource metrics
operationId: getCapsuleMetrics
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
description: |
Returns time-series CPU, memory, and disk metrics for a capsule.
Three tiers are available with different granularity and retention:
- `10m`: 500ms samples, last 10 minutes
- `2h`: 30-second averages, last 2 hours
- `24h`: 5-minute averages, last 24 hours
For running capsules, data comes from the host agent's in-memory
ring buffer. For paused capsules, data is read from persisted
snapshots in the database. Stopped/destroyed capsules return 404.
parameters:
- name: range
in: query
required: false
schema:
type: string
enum: ["5m", "10m", "1h", "2h", "6h", "12h", "24h"]
default: "10m"
description: Time range filter to query
responses:
"200":
description: Metrics retrieved
content:
application/json:
schema:
$ref: "#/components/schemas/CapsuleMetrics"
"400":
description: Invalid range parameter
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"404":
description: Capsule not found or metrics not available
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/pause:
parameters:
- name: id
in: path
required: true
schema:
type: string
post:
summary: Pause a running capsule
operationId: pauseCapsule
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
description: |
Takes a snapshot of the capsule (VM state + memory + rootfs), then
destroys all running resources. The capsule exists only as files on
disk and can be resumed later.
responses:
"202":
description: Capsule pause initiated (status will be "pausing")
content:
application/json:
schema:
$ref: "#/components/schemas/Capsule"
"409":
description: Capsule not running
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/resume:
parameters:
- name: id
in: path
required: true
schema:
type: string
post:
summary: Resume a paused capsule
operationId: resumeCapsule
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
description: |
Restores a paused capsule from its snapshot. Cloud Hypervisor is
relaunched in --restore mode with memory_restore_mode=ondemand so
guest pages fault in lazily via userfaultfd. The original network
slot (and host-reachable IP) is preserved across pause/resume.
responses:
"202":
description: Capsule resume initiated (status will be "resuming")
content:
application/json:
schema:
$ref: "#/components/schemas/Capsule"
"409":
description: Capsule not paused
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/snapshots:
post:
summary: Create a snapshot template
operationId: createSnapshot
tags: [snapshots]
security:
- apiKeyAuth: []
- sessionAuth: []
description: |
Snapshot a capsule, processed asynchronously. The call returns
immediately with the capsule in the `snapshotting` state, then it
returns to its original state on completion. The capsule must be
`running` or `paused`.
A `running` capsule is snapshotted live: it briefly pauses while its VM
state + memory + flattened rootfs are written to a new template, then
resumes to `running`. A `paused` capsule is snapshotted directly from
its on-disk state without reviving the VM, and stays `paused`.
Because it is async, the response does NOT contain the template. Watch
for the `template.snapshot.create` SSE event (its `outcome` reports
success or failure) or poll `GET /v1/snapshots` to observe completion.
Snapshots are immutable: each call must use a fresh name. Re-using
an existing name returns 409 Conflict.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateSnapshotRequest"
responses:
"202":
description: Snapshot accepted; capsule is now snapshotting
content:
application/json:
schema:
$ref: "#/components/schemas/Capsule"
"409":
description: Name already exists, or capsule is not running or paused
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
get:
summary: List templates for your team
operationId: listSnapshots
tags: [snapshots]
security:
- apiKeyAuth: []
- sessionAuth: []
parameters:
- name: type
in: query
required: false
schema:
type: string
enum: [base, snapshot]
description: Filter by template type.
responses:
"200":
description: List of templates
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Template"
/v1/snapshots/{name}:
parameters:
- name: name
in: path
required: true
schema:
type: string
delete:
summary: Delete a snapshot template
operationId: deleteSnapshot
tags: [snapshots]
security:
- apiKeyAuth: []
- sessionAuth: []
description: Removes the snapshot files from disk and deletes the database record.
responses:
"204":
description: Snapshot deleted
"404":
description: Template not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/files/write:
parameters:
- name: id
in: path
required: true
schema:
type: string
post:
summary: Upload a file
operationId: uploadFile
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [path, file]
properties:
path:
type: string
description: Absolute destination path inside the capsule
file:
type: string
format: binary
description: File content
responses:
"204":
description: File uploaded
"409":
description: Capsule not running
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"413":
description: File too large
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/files/read:
parameters:
- name: id
in: path
required: true
schema:
type: string
post:
summary: Download a file
operationId: downloadFile
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ReadFileRequest"
responses:
"200":
description: File content
content:
application/octet-stream:
schema:
type: string
format: binary
"404":
description: Capsule or file not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/files/list:
parameters:
- name: id
in: path
required: true
schema:
type: string
post:
summary: List directory contents
operationId: listDir
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ListDirRequest"
responses:
"200":
description: Directory listing
content:
application/json:
schema:
$ref: "#/components/schemas/ListDirResponse"
"404":
description: Capsule not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Capsule not running
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/files/mkdir:
parameters:
- name: id
in: path
required: true
schema:
type: string
post:
summary: Create a directory
operationId: makeDir
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/MakeDirRequest"
responses:
"200":
description: Directory created
content:
application/json:
schema:
$ref: "#/components/schemas/MakeDirResponse"
"404":
description: Capsule not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: >
Capsule not running, or a directory already exists at the
target path (error code `already_exists`).
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/files/remove:
parameters:
- name: id
in: path
required: true
schema:
type: string
post:
summary: Remove a file or directory
operationId: removePath
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RemoveRequest"
responses:
"204":
description: File or directory removed
"404":
description: Capsule not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Capsule not running
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/exec/stream:
parameters:
- name: id
in: path
required: true
schema:
type: string
get:
summary: Stream command execution via WebSocket
operationId: execStream
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
description: |
Opens a WebSocket connection for streaming command execution.
**Client sends** (first message to start the process):
```json
{"type": "start", "cmd": "tail", "args": ["-f", "/var/log/syslog"]}
```
**Client sends** (to stop the process):
```json
{"type": "stop"}
```
**Server sends** (process events as they arrive):
```json
{"type": "start", "pid": 1234}
{"type": "stdout", "data": "line of output\n"}
{"type": "stderr", "data": "warning message\n"}
{"type": "exit", "exit_code": 0}
{"type": "error", "data": "description of error"}
```
The connection closes automatically after the process exits.
responses:
"101":
description: WebSocket upgrade
"404":
description: Capsule not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Capsule not running
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/pty:
parameters:
- name: id
in: path
required: true
schema:
type: string
get:
summary: Interactive PTY session via WebSocket
operationId: ptySession
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
description: |
Opens a WebSocket connection for an interactive PTY (terminal) session.
Supports creating new sessions, sending input, resizing, killing, and
reconnecting to existing sessions.
**Client sends** (first message — start a new PTY):
```json
{
"type": "start",
"cmd": "/bin/bash",
"args": [],
"cols": 80,
"rows": 24,
"envs": {"TERM": "xterm-256color"},
"cwd": "/home/user",
"user": "user"
}
```
All fields except `type` are optional. Defaults: cmd="/bin/bash", cols=80, rows=24.
**Client sends** (first message — reconnect to existing PTY):
```json
{"type": "connect", "tag": "pty-abc123de"}
```
**Client sends** (after session is established):
```json
{"type": "input", "data": "<base64-encoded bytes>"}
{"type": "resize", "cols": 120, "rows": 40}
{"type": "kill"}
```
**Server sends**:
```json
{"type": "started", "tag": "pty-abc123de", "pid": 42}
{"type": "output", "data": "<base64-encoded PTY bytes>"}
{"type": "exit", "exit_code": 0}
{"type": "error", "data": "description", "fatal": true}
{"type": "ping"}
```
PTY data (input and output) is base64-encoded because it contains raw
terminal bytes (escape sequences, control codes) that are not valid UTF-8.
Sessions persist across WebSocket disconnections — the process keeps
running in the capsule. Use the `tag` from the "started" response to
reconnect later.
responses:
"101":
description: WebSocket upgrade
"404":
description: Capsule not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Capsule not running
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/files/stream/write:
parameters:
- name: id
in: path
required: true
schema:
type: string
post:
summary: Upload a file (streaming)
operationId: streamUploadFile
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
description: |
Streams file content to the capsule without buffering in memory.
Suitable for large files. Uses the same multipart/form-data format
as the non-streaming upload endpoint.
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [path, file]
properties:
path:
type: string
description: Absolute destination path inside the capsule
file:
type: string
format: binary
description: File content
responses:
"204":
description: File uploaded
"404":
description: Capsule not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Capsule not running
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/files/stream/read:
parameters:
- name: id
in: path
required: true
schema:
type: string
post:
summary: Download a file (streaming)
operationId: streamDownloadFile
tags: [capsules]
security:
- apiKeyAuth: []
- sessionAuth: []
description: |
Streams file content from the capsule without buffering in memory.
Suitable for large files. Returns raw bytes with chunked transfer encoding.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ReadFileRequest"
responses:
"200":
description: File content streamed in chunks
content:
application/octet-stream:
schema:
type: string
format: binary
"404":
description: Capsule or file not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Capsule not running
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/hosts:
post:
summary: Create a host
operationId: createHost
tags: [hosts]
security:
- sessionAuth: []
description: |
Creates a new host record and returns a one-time registration token.
Regular hosts can only be created by admins. BYOC hosts can be created
by admins or team owners.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateHostRequest"
responses:
"201":
description: Host created with registration token
content:
application/json:
schema:
$ref: "#/components/schemas/CreateHostResponse"
"400":
description: Invalid request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"403":
description: Insufficient permissions
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
get:
summary: List hosts
operationId: listHosts
tags: [hosts]
security:
- sessionAuth: []
description: |
Admins see all hosts. Non-admins see only BYOC hosts belonging to their team.
responses:
"200":
description: List of hosts
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Host"
/v1/hosts/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
get:
summary: Get host details
operationId: getHost
tags: [hosts]
security:
- sessionAuth: []
responses:
"200":
description: Host details
content:
application/json:
schema:
$ref: "#/components/schemas/Host"
"404":
description: Host not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
delete:
summary: Delete a host
operationId: deleteHost
tags: [hosts]
security:
- sessionAuth: []
description: |
Admins can delete any host. Team owners and admins can delete BYOC hosts
belonging to their team. Without `?force=true`, returns 409 if the host
has active capsules. With `?force=true`, destroys all capsules first.
parameters:
- name: force
in: query
required: false
schema:
type: boolean
description: If true, destroy all capsules on the host before deleting.
responses:
"204":
description: Host deleted
"403":
description: Insufficient permissions
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Host has active capsules (only when force is not set)
content:
application/json:
schema:
$ref: "#/components/schemas/HostHasCapsulesError"
/v1/hosts/{id}/token:
parameters:
- name: id
in: path
required: true
schema:
type: string
post:
summary: Regenerate registration token
operationId: regenerateHostToken
tags: [hosts]
security:
- sessionAuth: []
description: |
Issues a new registration token for a host still in "pending" status.
Use this when a previous registration attempt failed after consuming
the original token. Same permission model as host creation.
responses:
"201":
description: New registration token issued
content:
application/json:
schema:
$ref: "#/components/schemas/CreateHostResponse"
"403":
description: Insufficient permissions
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Host is not in pending status
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/hosts/register:
post:
summary: Register a host agent
operationId: registerHost
tags: [hosts]
description: |
Called by the host agent on first startup. Validates the one-time
registration token, records machine specs, sets the host status to
"online", and returns a long-lived JWT for subsequent API calls
(heartbeats).
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RegisterHostRequest"
responses:
"201":
description: Host registered, JWT returned
content:
application/json:
schema:
$ref: "#/components/schemas/RegisterHostResponse"
"400":
description: Invalid request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"401":
description: Invalid or expired registration token
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/hosts/{id}/heartbeat:
parameters:
- name: id
in: path
required: true
schema:
type: string
post:
summary: Host agent heartbeat
operationId: hostHeartbeat
tags: [hosts]
security:
- hostTokenAuth: []
description: |
Updates the host's last_heartbeat_at timestamp. The host ID in the URL
must match the host ID in the JWT.
responses:
"204":
description: Heartbeat recorded
"401":
description: Invalid or missing host token
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"403":
description: Host ID mismatch
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/hosts/sandbox-events:
post:
summary: Sandbox lifecycle event callback
operationId: sandboxEventCallback
tags: [hosts]
security:
- hostTokenAuth: []
description: |
Receives autonomous lifecycle events from host agents (e.g. auto-pause
from the TTL reaper). The event is published to an internal Redis stream
for the control plane's event consumer to process.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [event, sandbox_id, host_id]
properties:
event:
type: string
description: |
Lifecycle event type. Known values:
* `sandbox.auto_paused` — TTL reaper paused the capsule
* `sandbox.stopped` — autonomous destroy (crash/eviction)
* `sandbox.error` — VMM/crash watcher reported error
Unknown event names are accepted and forwarded to the
stream consumer as-is (future-compatible).
sandbox_id:
type: string
host_id:
type: string
timestamp:
type: integer
format: int64
responses:
"204":
description: Event accepted
"400":
description: Invalid request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"403":
description: Host ID mismatch
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/hosts/auth/refresh:
post:
summary: Refresh host JWT
operationId: refreshHostToken
tags: [hosts]
description: |
Exchanges a refresh token for a new JWT and rotated refresh token.
The old refresh token is immediately revoked. No authentication required —
the refresh token itself is the credential.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RefreshHostTokenRequest"
responses:
"200":
description: New JWT and rotated refresh token
content:
application/json:
schema:
$ref: "#/components/schemas/RefreshHostTokenResponse"
"401":
description: Invalid, expired, or revoked refresh token
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/hosts/{id}/delete-preview:
parameters:
- name: id
in: path
required: true
schema:
type: string
get:
summary: Preview host deletion
operationId: getHostDeletePreview
tags: [hosts]
security:
- sessionAuth: []
description: |
Returns the list of capsule IDs that would be destroyed if the host
were deleted with `?force=true`. No state is modified.
responses:
"200":
description: Deletion preview
content:
application/json:
schema:
$ref: "#/components/schemas/HostDeletePreview"
"403":
description: Insufficient permissions
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"404":
description: Host not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/hosts/{id}/tags:
parameters:
- name: id
in: path
required: true
schema:
type: string
get:
summary: List host tags
operationId: listHostTags
tags: [hosts]
security:
- sessionAuth: []
responses:
"200":
description: List of tags
content:
application/json:
schema:
type: array
items:
type: string
post:
summary: Add a tag to a host
operationId: addHostTag
tags: [hosts]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AddTagRequest"
responses:
"204":
description: Tag added
"404":
description: Host not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/hosts/{id}/tags/{tag}:
parameters:
- name: id
in: path
required: true
schema:
type: string
- name: tag
in: path
required: true
schema:
type: string
delete:
summary: Remove a tag from a host
operationId: removeHostTag
tags: [hosts]
security:
- sessionAuth: []
responses:
"204":
description: Tag removed
"404":
description: Host not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/channels:
post:
summary: Create a notification channel
operationId: createChannel
tags: [channels]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateChannelRequest"
responses:
"201":
description: Channel created
content:
application/json:
schema:
$ref: "#/components/schemas/ChannelResponse"
"400":
$ref: "#/components/responses/BadRequest"
get:
summary: List notification channels
operationId: listChannels
tags: [channels]
security:
- sessionAuth: []
responses:
"200":
description: Channels list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ChannelResponse"
/v1/channels/test:
post:
summary: Test a channel configuration
description: >
Sends a test notification using the provided provider and config without
saving anything. Use this to verify credentials before creating a channel.
operationId: testChannel
tags: [channels]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TestChannelRequest"
responses:
"200":
description: Test notification sent successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: ok
"400":
$ref: "#/components/responses/BadRequest"
/v1/channels/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
get:
summary: Get a notification channel
operationId: getChannel
tags: [channels]
security:
- sessionAuth: []
responses:
"200":
description: Channel details
content:
application/json:
schema:
$ref: "#/components/schemas/ChannelResponse"
"404":
description: Channel not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
patch:
summary: Update a notification channel
operationId: updateChannel
tags: [channels]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateChannelRequest"
responses:
"200":
description: Channel updated
content:
application/json:
schema:
$ref: "#/components/schemas/ChannelResponse"
"400":
$ref: "#/components/responses/BadRequest"
"404":
description: Channel not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
delete:
summary: Delete a notification channel
operationId: deleteChannel
tags: [channels]
security:
- sessionAuth: []
responses:
"204":
description: Channel deleted
/v1/channels/{id}/config:
parameters:
- name: id
in: path
required: true
schema:
type: string
put:
summary: Rotate channel secrets
description: >
Replaces the channel's provider configuration entirely with new secrets.
The previous config is discarded. Config fields must match the provider's
required fields.
operationId: rotateChannelConfig
tags: [channels]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RotateConfigRequest"
responses:
"200":
description: Config rotated
content:
application/json:
schema:
$ref: "#/components/schemas/ChannelResponse"
"400":
$ref: "#/components/responses/BadRequest"
"404":
description: Channel not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/admin/users/{id}/admin:
put:
summary: Grant or revoke platform admin
operationId: setUserAdmin
tags: [admin]
description: |
Sets the platform admin flag on a user. Cannot remove the last admin.
Requires platform admin access. Session caches for the target user
are invalidated immediately so the flag flip takes effect on the
user's next request.
security:
- sessionAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
example: "usr-a1b2c3d4"
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [admin]
properties:
admin:
type: boolean
description: true to grant admin, false to revoke.
responses:
"204":
description: Admin status updated
"400":
$ref: "#/components/responses/BadRequest"
"403":
description: Caller is not a platform admin
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"404":
description: User not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/events/stream:
get:
summary: Real-time lifecycle event stream
operationId: streamEvents
tags: [events]
description: |
Server-Sent Events stream of capsule, template, and host lifecycle
events scoped to the caller's active team. Browsers send the
wrenn_sid cookie automatically on EventSource connections; SDKs
authenticate via X-API-Key.
Frame format follows the standard SSE protocol:
```
event: capsule.create
data: {"event":"capsule.create","outcome":"success","resource":{"id":"sb-..."},"sandbox":{...},"timestamp":"2026-05-19T02:00:00Z"}
: keepalive
```
A `: keepalive` comment is emitted every 30s.
security:
- apiKeyAuth: []
- sessionAuth: []
responses:
"200":
description: SSE stream opened
content:
text/event-stream:
schema:
$ref: "#/components/schemas/SSEEvent"
"401":
description: Missing or invalid auth
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/audit-logs:
get:
summary: List team audit log entries
operationId: listAuditLogs
tags: [audit]
description: Paginated cursor list of audit events for the caller's team.
security:
- sessionAuth: []
parameters:
- name: before
in: query
required: false
schema:
type: string
format: date-time
- name: before_id
in: query
required: false
schema:
type: string
- name: limit
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 200
default: 50
responses:
"200":
description: Audit log page
content:
application/json:
schema:
type: object
properties:
entries:
type: array
items:
$ref: "#/components/schemas/AuditLogEntry"
next_cursor:
type: object
nullable: true
properties:
before:
type: string
format: date-time
before_id:
type: string
/v1/admin/events/stream:
get:
summary: Admin SSE event stream (all teams)
operationId: adminStreamEvents
tags: [admin, events]
description: |
Admin variant of /v1/events/stream that emits events across all teams.
Requires an admin session cookie.
security:
- sessionAuth: []
responses:
"200":
description: SSE stream opened
content:
text/event-stream:
schema:
$ref: "#/components/schemas/SSEEvent"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/v1/admin/audit-logs:
get:
summary: List audit log entries (all teams)
operationId: adminListAuditLogs
tags: [admin, audit]
security:
- sessionAuth: []
parameters:
- name: before
in: query
schema: {type: string, format: date-time}
- name: before_id
in: query
schema: {type: string}
- name: limit
in: query
schema: {type: integer, minimum: 1, maximum: 200, default: 50}
responses:
"200":
description: Audit log page (all teams)
content:
application/json:
schema:
type: object
properties:
entries:
type: array
items:
$ref: "#/components/schemas/AuditLogEntry"
/v1/admin/teams:
get:
summary: List all teams (admin)
operationId: adminListTeams
tags: [admin]
security:
- sessionAuth: []
parameters:
- name: page
in: query
required: false
schema:
type: integer
minimum: 1
default: 1
description: Page number for pagination.
responses:
"200":
description: Paginated teams list
content:
application/json:
schema:
type: object
properties:
teams:
type: array
items:
$ref: "#/components/schemas/AdminTeam"
total:
type: integer
page:
type: integer
per_page:
type: integer
total_pages:
type: integer
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/v1/admin/teams/{id}/byoc:
put:
summary: Toggle BYOC for a team (admin)
operationId: adminSetTeamBYOC
tags: [admin]
security:
- sessionAuth: []
parameters:
- name: id
in: path
required: true
schema: {type: string}
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [enabled]
properties:
enabled:
type: boolean
description: true to enable BYOC, false to disable.
responses:
"204":
description: Updated
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/v1/admin/teams/{id}:
delete:
summary: Delete a team (admin)
operationId: adminDeleteTeam
tags: [admin]
security:
- sessionAuth: []
parameters:
- name: id
in: path
required: true
schema: {type: string}
responses:
"204":
description: Deleted
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/admin/hosts:
get:
summary: List all hosts (admin)
operationId: adminListHosts
tags: [admin]
security:
- sessionAuth: []
description: |
Returns all hosts across all teams with per-host resource consumption.
Includes team name for hosts associated with a team.
responses:
"200":
description: Hosts list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Host"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/v1/admin/users:
get:
summary: List all users (admin)
operationId: adminListUsers
tags: [admin]
security:
- sessionAuth: []
responses:
"200":
description: Users list
content:
application/json:
schema:
type: array
items: {type: object}
/v1/admin/users/{id}/active:
put:
summary: Activate or deactivate a user (admin)
operationId: adminSetUserActive
tags: [admin]
security:
- sessionAuth: []
parameters:
- name: id
in: path
required: true
schema: {type: string}
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [active]
properties:
active: {type: boolean}
responses:
"204":
description: Updated
/v1/admin/templates:
get:
summary: List all templates (admin)
operationId: adminListTemplates
tags: [admin]
security:
- sessionAuth: []
responses:
"200":
description: Templates list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/AdminTemplate"
/v1/admin/templates/{name}:
delete:
summary: Delete a template (admin)
operationId: adminDeleteTemplate
tags: [admin]
security:
- sessionAuth: []
parameters:
- name: name
in: path
required: true
schema: {type: string}
responses:
"204":
description: Deleted
/v1/admin/builds:
post:
summary: Submit a template build (admin)
operationId: adminCreateBuild
tags: [admin]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema: {type: object}
responses:
"202":
description: Build queued
content:
application/json:
schema: {type: object}
get:
summary: List builds (admin)
operationId: adminListBuilds
tags: [admin]
security:
- sessionAuth: []
responses:
"200":
description: Builds list
content:
application/json:
schema:
type: array
items: {type: object}
/v1/admin/builds/{id}:
get:
summary: Get build detail (admin)
operationId: adminGetBuild
tags: [admin]
security:
- sessionAuth: []
parameters:
- name: id
in: path
required: true
schema: {type: string}
responses:
"200":
description: Build detail
content:
application/json:
schema: {type: object}
/v1/admin/builds/{id}/cancel:
post:
summary: Cancel a build (admin)
operationId: adminCancelBuild
tags: [admin]
security:
- sessionAuth: []
parameters:
- name: id
in: path
required: true
schema: {type: string}
responses:
"204":
description: Cancelled
/v1/admin/builds/{id}/stream:
get:
summary: Stream a build's live console (admin, WebSocket)
description: >
WebSocket endpoint. On connect, replays the completed-step history,
then live-tails JSON events (step-start, output, step-end,
build-status, ping) until the build finishes.
operationId: adminStreamBuild
tags: [admin]
security:
- sessionAuth: []
parameters:
- name: id
in: path
required: true
schema: {type: string}
responses:
"101":
description: WebSocket upgrade — streams build console events
/v1/admin/capsules:
post:
summary: Create a capsule on behalf of any team (admin)
operationId: adminCreateCapsule
tags: [admin]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateCapsuleRequest"
responses:
"201":
description: Capsule created
content:
application/json:
schema:
$ref: "#/components/schemas/Capsule"
get:
summary: List capsules across all teams (admin)
operationId: adminListCapsules
tags: [admin]
security:
- sessionAuth: []
responses:
"200":
description: Capsules list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Capsule"
/v1/admin/capsules/{id}:
parameters:
- name: id
in: path
required: true
schema: {type: string}
get:
summary: Get capsule detail (admin)
operationId: adminGetCapsule
tags: [admin]
security:
- sessionAuth: []
responses:
"200":
description: Capsule detail
content:
application/json:
schema:
$ref: "#/components/schemas/Capsule"
delete:
summary: Destroy capsule (admin)
operationId: adminDestroyCapsule
tags: [admin]
security:
- sessionAuth: []
responses:
"204":
description: Destroyed
/v1/admin/capsules/{id}/snapshot:
post:
summary: Create snapshot from any capsule (admin)
operationId: adminCreateSnapshotFromCapsule
tags: [admin]
description: |
Snapshots a `running` or `paused` capsule into a platform template,
processed asynchronously (see `POST /v1/snapshots`). A running capsule
resumes to `running`; a paused capsule stays `paused`.
security:
- sessionAuth: []
parameters:
- name: id
in: path
required: true
schema: {type: string}
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: Optional; an auto-generated name is used when omitted.
responses:
"202":
description: Snapshot accepted; capsule is now snapshotting
content:
application/json:
schema:
$ref: "#/components/schemas/Capsule"
/v1/admin/capsules/{id}/exec:
parameters:
- name: id
in: path
required: true
schema: {type: string}
post:
summary: Execute a command on any capsule (admin)
operationId: adminExecCommand
tags: [admin]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ExecRequest"
responses:
"200":
description: Command output (foreground exec)
content:
application/json:
schema:
$ref: "#/components/schemas/ExecResponse"
"202":
description: Background process started
content:
application/json:
schema:
$ref: "#/components/schemas/BackgroundExecResponse"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/FailedPrecondition"
/v1/admin/capsules/{id}/metrics:
parameters:
- name: id
in: path
required: true
schema: {type: string}
get:
summary: Get per-capsule resource metrics (admin)
operationId: adminGetCapsuleMetrics
tags: [admin]
security:
- sessionAuth: []
parameters:
- name: range
in: query
required: false
schema:
type: string
enum: ["5m", "10m", "1h", "2h", "6h", "12h", "24h"]
default: "10m"
responses:
"200":
description: Metrics retrieved
content:
application/json:
schema:
$ref: "#/components/schemas/CapsuleMetrics"
"404":
$ref: "#/components/responses/NotFound"
/v1/admin/capsules/{id}/processes:
parameters:
- name: id
in: path
required: true
schema: {type: string}
get:
summary: List running processes on any capsule (admin)
operationId: adminListProcesses
tags: [admin]
security:
- sessionAuth: []
responses:
"200":
description: Process list
content:
application/json:
schema:
$ref: "#/components/schemas/ProcessListResponse"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/FailedPrecondition"
/v1/admin/capsules/{id}/processes/{selector}:
parameters:
- name: id
in: path
required: true
schema: {type: string}
- name: selector
in: path
required: true
schema: {type: string}
description: Process PID (numeric) or tag (string)
delete:
summary: Kill a process on any capsule (admin)
operationId: adminKillProcess
tags: [admin]
security:
- sessionAuth: []
parameters:
- name: signal
in: query
required: false
schema:
type: string
enum: [SIGKILL, SIGTERM]
default: SIGKILL
responses:
"204":
description: Process killed
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/FailedPrecondition"
/v1/admin/capsules/{id}/files/write:
parameters:
- name: id
in: path
required: true
schema: {type: string}
post:
summary: Upload a file to any capsule (admin)
operationId: adminUploadFile
tags: [admin]
security:
- sessionAuth: []
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [path, file]
properties:
path: {type: string}
file: {type: string, format: binary}
responses:
"204":
description: File uploaded
"409":
$ref: "#/components/responses/FailedPrecondition"
/v1/admin/capsules/{id}/files/read:
parameters:
- name: id
in: path
required: true
schema: {type: string}
post:
summary: Download a file from any capsule (admin)
operationId: adminDownloadFile
tags: [admin]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ReadFileRequest"
responses:
"200":
description: File content
content:
application/octet-stream:
schema:
type: string
format: binary
"404":
$ref: "#/components/responses/NotFound"
/v1/admin/capsules/{id}/files/list:
parameters:
- name: id
in: path
required: true
schema: {type: string}
post:
summary: List directory contents on any capsule (admin)
operationId: adminListDir
tags: [admin]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ListDirRequest"
responses:
"200":
description: Directory listing
content:
application/json:
schema:
$ref: "#/components/schemas/ListDirResponse"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/FailedPrecondition"
/v1/admin/capsules/{id}/files/mkdir:
parameters:
- name: id
in: path
required: true
schema: {type: string}
post:
summary: Create a directory on any capsule (admin)
operationId: adminMakeDir
tags: [admin]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/MakeDirRequest"
responses:
"200":
description: Directory created
content:
application/json:
schema:
$ref: "#/components/schemas/MakeDirResponse"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/FailedPrecondition"
/v1/admin/capsules/{id}/files/remove:
parameters:
- name: id
in: path
required: true
schema: {type: string}
post:
summary: Remove a file or directory on any capsule (admin)
operationId: adminRemovePath
tags: [admin]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RemoveRequest"
responses:
"204":
description: File or directory removed
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/FailedPrecondition"
/v1/admin/capsules/{id}/exec/stream:
parameters:
- name: id
in: path
required: true
schema: {type: string}
get:
summary: Stream command execution on any capsule via WebSocket (admin)
operationId: adminExecStream
tags: [admin]
security:
- sessionAuth: []
description: |
Admin variant of /v1/capsules/{id}/exec/stream. Same protocol — WebSocket
upgrade, client sends `{"type":"start", "cmd":..., "args":...}` to start;
server streams stdout/stderr/exit frames.
responses:
"101":
description: WebSocket upgrade
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/FailedPrecondition"
/v1/admin/capsules/{id}/pty:
parameters:
- name: id
in: path
required: true
schema: {type: string}
get:
summary: Interactive PTY session on any capsule via WebSocket (admin)
operationId: adminPtySession
tags: [admin]
security:
- sessionAuth: []
description: |
Admin variant of /v1/capsules/{id}/pty. Same protocol — base64-encoded
PTY bytes, start/connect/input/resize/kill control messages, persistent
sessions reconnectable via tag.
responses:
"101":
description: WebSocket upgrade
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/FailedPrecondition"
/v1/admin/capsules/{id}/processes/{selector}/stream:
parameters:
- name: id
in: path
required: true
schema: {type: string}
- name: selector
in: path
required: true
schema: {type: string}
description: Process PID (numeric) or tag (string)
get:
summary: Stream process output on any capsule via WebSocket (admin)
operationId: adminConnectProcess
tags: [admin]
security:
- sessionAuth: []
responses:
"101":
description: WebSocket upgrade
"404":
$ref: "#/components/responses/NotFound"
components:
responses:
BadRequest:
description: Invalid request parameters
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
Unauthorized:
description: Missing or invalid auth
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
Forbidden:
description: Authenticated but not permitted (e.g. non-admin on /v1/admin/*)
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
FailedPrecondition:
description: Resource state does not allow this operation (e.g. exec on a paused capsule)
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
securitySchemes:
apiKeyAuth:
type: apiKey
in: header
name: X-API-Key
description: API key for capsule lifecycle operations. Create via POST /v1/api-keys.
sessionAuth:
type: apiKey
in: cookie
name: wrenn_sid
description: |
Opaque session cookie set by POST /v1/auth/login, /v1/auth/activate, or
the OAuth callback. HttpOnly, Secure, SameSite=Strict. Idle window 6h,
absolute lifetime 24h. State-changing requests also require an
X-CSRF-Token header matching the wrenn_csrf cookie (double-submit).
csrfHeader:
type: apiKey
in: header
name: X-CSRF-Token
description: |
Double-submit CSRF token whose value must match the wrenn_csrf cookie.
Required on all non-GET requests authenticated via session cookie.
Not required for API key auth.
hostTokenAuth:
type: apiKey
in: header
name: X-Host-Token
description: Host JWT returned from POST /v1/hosts/register or POST /v1/hosts/auth/refresh. Valid for 7 days.
schemas:
SignupRequest:
type: object
required: [email, password, name]
properties:
email:
type: string
format: email
password:
type: string
minLength: 8
name:
type: string
maxLength: 100
LoginRequest:
type: object
required: [email, password]
properties:
email:
type: string
format: email
password:
type: string
SignupResponse:
type: object
properties:
message:
type: string
description: Confirmation message instructing user to check email
SessionResponse:
type: object
description: |
Returned by login, activate, and switch-team. The actual auth credential
is the wrenn_sid cookie set on the response. The body carries identity
data the SPA needs to bootstrap.
properties:
user_id:
type: string
team_id:
type: string
email:
type: string
name:
type: string
role:
type: string
is_admin:
type: boolean
CreateAPIKeyRequest:
type: object
properties:
name:
type: string
default: Unnamed API Key
APIKeyResponse:
type: object
properties:
id:
type: string
team_id:
type: string
name:
type: string
key_prefix:
type: string
description: Display prefix (e.g. "wrn_ab12cd34...")
created_at:
type: string
format: date-time
last_used:
type: string
format: date-time
nullable: true
key:
type: string
description: Full plaintext key. Only returned on creation, never again.
nullable: true
CreateCapsuleRequest:
type: object
properties:
template:
type: string
default: minimal-ubuntu
vcpus:
type: integer
default: 1
memory_mb:
type: integer
default: 512
disk_size_mb:
type: integer
default: 5120
description: >
Maximum size of the per-capsule copy-on-write disk in MB. Capped
at 5 GB by default; the actual size is max(disk_size_mb, origin
rootfs size).
timeout_sec:
type: integer
minimum: 0
default: 0
description: >
Auto-pause TTL in seconds. The capsule is automatically paused
after this duration of inactivity (no exec or ping). 0 means
no auto-pause. Positive values below 60 are silently clamped
to 60 (the agent's startup envelope).
UsageResponse:
type: object
properties:
from:
type: string
format: date
to:
type: string
format: date
points:
type: array
items:
type: object
properties:
date:
type: string
format: date
cpu_minutes:
type: number
ram_mb_minutes:
type: number
CapsuleStats:
type: object
properties:
range:
type: string
enum: [5m, 1h, 6h, 24h, 30d]
current:
type: object
properties:
running_count:
type: integer
vcpus_reserved:
type: integer
memory_mb_reserved:
type: integer
peaks:
type: object
description: Maximum values over the last 30 days.
properties:
running_count:
type: integer
vcpus:
type: integer
memory_mb:
type: integer
series:
type: object
description: Parallel arrays for chart rendering.
properties:
labels:
type: array
items:
type: string
format: date-time
running:
type: array
items:
type: integer
vcpus:
type: array
items:
type: integer
memory_mb:
type: array
items:
type: integer
Capsule:
type: object
properties:
id:
type: string
status:
type: string
enum: [pending, starting, running, pausing, paused, snapshotting, resuming, stopping, hibernated, stopped, missing, error]
template:
type: string
vcpus:
type: integer
memory_mb:
type: integer
timeout_sec:
type: integer
created_at:
type: string
format: date-time
started_at:
type: string
format: date-time
nullable: true
last_active_at:
type: string
format: date-time
nullable: true
last_updated:
type: string
format: date-time
metadata:
type: object
additionalProperties: {type: string}
nullable: true
description: |
Free-form key/value labels attached at create-time. Also carries
agent-side version info (kernel_version, vmm_version,
agent_version, envd_version) when running.
disk_size_mb:
type: integer
description: Maximum disk capacity in MiB.
disk_used_mb:
type: integer
format: int64
description: Current disk usage in MiB. Only populated on individual capsule GET; omitted in list responses.
CreateSnapshotRequest:
type: object
required: [sandbox_id]
properties:
sandbox_id:
type: string
description: ID of the running capsule to snapshot.
name:
type: string
description: Name for the snapshot template. Auto-generated if omitted.
Template:
type: object
properties:
name:
type: string
type:
type: string
enum: [base, snapshot]
vcpus:
type: integer
nullable: true
memory_mb:
type: integer
nullable: true
size_bytes:
type: integer
format: int64
created_at:
type: string
format: date-time
platform:
type: boolean
description: |
True when the template is platform-managed (visible to all teams,
e.g. the built-in `minimal-ubuntu` rootfs). False for team-owned
snapshot templates.
protected:
type: boolean
description: |
True for built-in system base templates (minimal-ubuntu,
minimal-alpine, minimal-arch, minimal-fedora). Protected templates
cannot be deleted.
metadata:
type: object
additionalProperties: {type: string}
nullable: true
AdminTemplate:
type: object
description: |
Template as returned by the admin templates list. Unlike `Template`
(the team-facing snapshot shape), this includes the owning `team_id`
and omits `platform`/`metadata`.
properties:
name:
type: string
type:
type: string
enum: [base, snapshot]
vcpus:
type: integer
memory_mb:
type: integer
size_bytes:
type: integer
format: int64
team_id:
type: string
description: Owning team ID (formatted, e.g. `team-…`). Platform team for global templates.
created_at:
type: string
format: date-time
protected:
type: boolean
description: |
True for built-in system base templates (minimal-ubuntu,
minimal-alpine, minimal-arch, minimal-fedora). Protected templates
cannot be deleted.
ExecRequest:
type: object
required: [cmd]
properties:
cmd:
type: string
args:
type: array
items:
type: string
timeout_sec:
type: integer
default: 30
description: Timeout in seconds (foreground exec only, default 30)
background:
type: boolean
default: false
description: If true, starts the process in the background and returns immediately with a PID and tag (HTTP 202)
tag:
type: string
description: Optional user-chosen tag for the background process. Auto-generated if omitted. Only used when background is true.
envs:
type: object
additionalProperties:
type: string
description: Environment variables for the process (background exec only)
cwd:
type: string
description: Working directory for the process (background exec only)
BackgroundExecResponse:
type: object
properties:
sandbox_id:
type: string
cmd:
type: string
pid:
type: integer
tag:
type: string
ProcessEntry:
type: object
properties:
pid:
type: integer
tag:
type: string
cmd:
type: string
args:
type: array
items:
type: string
ProcessListResponse:
type: object
properties:
processes:
type: array
items:
$ref: "#/components/schemas/ProcessEntry"
ExecResponse:
type: object
properties:
sandbox_id:
type: string
cmd:
type: string
stdout:
type: string
stderr:
type: string
exit_code:
type: integer
duration_ms:
type: integer
encoding:
type: string
enum: [utf-8, base64]
description: Output encoding. "base64" when stdout/stderr contain binary data.
ReadFileRequest:
type: object
required: [path]
properties:
path:
type: string
description: Absolute file path inside the capsule
ListDirRequest:
type: object
required: [path]
properties:
path:
type: string
description: Directory path inside the capsule
depth:
type: integer
default: 1
description: Recursion depth (0 = non-recursive, 1 = immediate children)
ListDirResponse:
type: object
properties:
entries:
type: array
items:
$ref: "#/components/schemas/FileEntry"
FileEntry:
type: object
properties:
name:
type: string
path:
type: string
type:
type: string
enum: [file, directory, symlink]
size:
type: integer
format: int64
mode:
type: integer
permissions:
type: string
description: Human-readable permissions (e.g. "-rwxr-xr-x")
owner:
type: string
group:
type: string
modified_at:
type: integer
format: int64
description: Unix timestamp (seconds)
symlink_target:
type: string
nullable: true
MakeDirRequest:
type: object
required: [path]
properties:
path:
type: string
description: Directory path to create inside the capsule
MakeDirResponse:
type: object
properties:
entry:
$ref: "#/components/schemas/FileEntry"
RemoveRequest:
type: object
required: [path]
properties:
path:
type: string
description: Path to remove inside the capsule
CreateHostRequest:
type: object
required: [type]
properties:
type:
type: string
enum: [regular, byoc]
description: Host type. Regular hosts are shared; BYOC hosts belong to a team.
team_id:
type: string
description: Required for BYOC hosts.
provider:
type: string
description: Cloud provider (e.g. aws, gcp, hetzner, bare-metal).
availability_zone:
type: string
description: Availability zone (e.g. us-east, eu-west).
CreateHostResponse:
type: object
properties:
host:
$ref: "#/components/schemas/Host"
registration_token:
type: string
description: One-time registration token for the host agent. Expires in 1 hour.
RegisterHostRequest:
type: object
required: [token, address]
properties:
token:
type: string
description: One-time registration token from POST /v1/hosts.
arch:
type: string
description: CPU architecture (e.g. x86_64, aarch64).
cpu_cores:
type: integer
memory_mb:
type: integer
disk_gb:
type: integer
address:
type: string
description: Host agent address (ip:port).
RegisterHostResponse:
type: object
properties:
host:
$ref: "#/components/schemas/Host"
token:
type: string
description: Host JWT for X-Host-Token header. Valid for 7 days.
refresh_token:
type: string
description: Refresh token for obtaining new JWTs. Valid for 60 days; rotated on each use.
Host:
type: object
properties:
id:
type: string
type:
type: string
enum: [regular, byoc]
team_id:
type: string
nullable: true
provider:
type: string
nullable: true
availability_zone:
type: string
nullable: true
arch:
type: string
nullable: true
cpu_cores:
type: integer
nullable: true
memory_mb:
type: integer
nullable: true
disk_gb:
type: integer
nullable: true
address:
type: string
nullable: true
status:
type: string
enum: [pending, online, offline, draining, unreachable]
last_heartbeat_at:
type: string
format: date-time
nullable: true
created_by:
type: string
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
team_name:
type: string
nullable: true
description: Team name (included when listing hosts as an admin).
running_vcpus:
type: integer
description: Total vCPUs allocated to running capsules on this host.
running_memory_mb:
type: integer
description: Total memory in MB allocated to running capsules on this host.
running_disk_mb:
type: integer
description: Total disk in MB allocated to running capsules on this host.
paused_memory_mb:
type: integer
description: Total memory in MB allocated to paused capsules on this host.
paused_disk_mb:
type: integer
description: Total disk in MB allocated to paused capsules on this host.
RefreshHostTokenRequest:
type: object
required: [refresh_token]
properties:
refresh_token:
type: string
description: Refresh token obtained from registration or a previous refresh.
RefreshHostTokenResponse:
type: object
properties:
host:
$ref: "#/components/schemas/Host"
token:
type: string
description: New host JWT. Valid for 7 days.
refresh_token:
type: string
description: New refresh token. Valid for 60 days; old token is revoked.
HostDeletePreview:
type: object
properties:
host:
$ref: "#/components/schemas/Host"
sandbox_ids:
type: array
items:
type: string
description: IDs of capsules that would be destroyed on force-delete.
HostHasCapsulesError:
type: object
properties:
error:
type: object
properties:
code:
type: string
example: host_has_sandboxes
message:
type: string
sandbox_ids:
type: array
items:
type: string
description: IDs of active capsules blocking deletion.
AddTagRequest:
type: object
required: [tag]
properties:
tag:
type: string
UserSearchResult:
type: object
properties:
user_id:
type: string
email:
type: string
Team:
type: object
properties:
id:
type: string
name:
type: string
slug:
type: string
description: Immutable 12-char hex slug (e.g. a1b2c3-d1e2f3)
created_at:
type: string
format: date-time
TeamWithRole:
allOf:
- $ref: "#/components/schemas/Team"
- type: object
properties:
role:
type: string
enum: [owner, admin, member]
TeamMember:
type: object
properties:
user_id:
type: string
email:
type: string
role:
type: string
enum: [owner, admin, member]
joined_at:
type: string
format: date-time
TeamDetail:
type: object
properties:
team:
$ref: "#/components/schemas/Team"
members:
type: array
items:
$ref: "#/components/schemas/TeamMember"
AdminTeam:
type: object
properties:
id:
type: string
name:
type: string
slug:
type: string
is_byoc:
type: boolean
created_at:
type: string
format: date-time
deleted_at:
type: string
format: date-time
nullable: true
member_count:
type: integer
owner_name:
type: string
owner_email:
type: string
active_sandbox_count:
type: integer
channel_count:
type: integer
running_vcpus:
type: integer
running_memory_mb:
type: integer
CapsuleMetrics:
type: object
properties:
sandbox_id:
type: string
range:
type: string
enum: ["5m", "10m", "1h", "2h", "6h", "12h", "24h"]
points:
type: array
items:
$ref: "#/components/schemas/MetricPoint"
MetricPoint:
type: object
properties:
timestamp_unix:
type: integer
format: int64
cpu_pct:
type: number
format: double
description: "CPU utilization percentage (0-100), normalized to vCPU count"
mem_bytes:
type: integer
format: int64
description: "Resident memory in bytes (VmRSS of Cloud Hypervisor process)"
disk_bytes:
type: integer
format: int64
description: "Allocated disk bytes for the CoW sparse file"
CreateChannelRequest:
type: object
required: [name, provider, config, events]
properties:
name:
type: string
description: Unique channel name within the team.
provider:
type: string
enum: [discord, slack, teams, googlechat, telegram, matrix, webhook]
config:
type: object
additionalProperties:
type: string
description: >
Provider-specific configuration fields.
Discord/Slack/Teams/Google Chat: {"webhook_url": "..."}.
Telegram: {"bot_token": "...", "chat_id": "..."}.
Matrix: {"homeserver_url": "...", "access_token": "...", "room_id": "..."}.
Webhook: {"url": "...", "secret": "..."} (secret is auto-generated if omitted).
events:
type: array
items:
type: string
enum:
- capsule.create
- capsule.pause
- capsule.resume
- capsule.destroy
- template.snapshot.create
- template.snapshot.delete
- host.up
- host.down
TestChannelRequest:
type: object
required: [provider, config]
properties:
provider:
type: string
enum: [discord, slack, teams, googlechat, telegram, matrix, webhook]
config:
type: object
additionalProperties:
type: string
description: Provider-specific configuration fields (same as CreateChannelRequest.config).
RotateConfigRequest:
type: object
required: [config]
properties:
config:
type: object
additionalProperties:
type: string
description: >
New provider configuration fields. Must include all required fields
for the channel's provider. Replaces the existing config entirely.
UpdateChannelRequest:
type: object
required: [name, events]
properties:
name:
type: string
events:
type: array
items:
type: string
enum:
- capsule.create
- capsule.pause
- capsule.resume
- capsule.destroy
- template.snapshot.create
- template.snapshot.delete
- host.up
- host.down
ChannelResponse:
type: object
properties:
id:
type: string
team_id:
type: string
name:
type: string
provider:
type: string
enum: [discord, slack, teams, googlechat, telegram, matrix, webhook]
events:
type: array
items:
type: string
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
secret:
type: string
nullable: true
description: Webhook secret. Only returned on creation, never again.
MeResponse:
type: object
properties:
name:
type: string
email:
type: string
format: email
has_password:
type: boolean
description: Whether the user has a password set (false for OAuth-only accounts)
providers:
type: array
items:
type: string
description: List of linked OAuth provider names (e.g. ["github"])
ChangePasswordRequest:
type: object
required: [new_password]
properties:
current_password:
type: string
description: Required when changing an existing password
new_password:
type: string
minLength: 8
confirm_password:
type: string
description: Required when adding a password to an OAuth-only account (must match new_password)
Error:
type: object
properties:
error:
type: object
properties:
code:
type: string
message:
type: string
AuditLogEntry:
type: object
properties:
id: {type: string}
actor_type: {type: string, enum: [user, api_key, host, system]}
actor_id: {type: string}
actor_name: {type: string}
resource_type: {type: string}
resource_id: {type: string}
action: {type: string}
scope: {type: string}
status: {type: string, enum: [success, failure]}
metadata:
type: object
additionalProperties: true
created_at:
type: string
format: date-time
SSEEvent:
type: object
description: |
Wire format of one SSE message body. The event name (`event:` line) is
the `kind` and the JSON below is the `data:` line.
properties:
event:
type: string
enum:
- connected
- capsule.create
- capsule.pause
- capsule.resume
- capsule.destroy
- capsule.state.changed
- template.snapshot.create
- template.snapshot.delete
- host.up
- host.down
outcome:
type: string
enum: [success, error]
description: |
Present for action events (capsule.* except state.changed,
template.snapshot.*). Absent for host.up/down, capsule.state.changed,
and the connected sentinel.
resource:
type: object
properties:
id: {type: string}
type: {type: string}
actor:
type: object
properties:
type: {type: string, enum: [user, api_key, system]}
id: {type: string}
name: {type: string}
metadata:
type: object
additionalProperties: {type: string}
description: |
Event-specific context. Examples: `reason` (ttl_expired,
host_failure, cleanup_after_create_error, orphaned),
`host_ip`, `from`/`to` (for capsule.state.changed).
error:
type: string
description: Failure reason; only set when outcome=error.
sandbox:
allOf:
- $ref: "#/components/schemas/Capsule"
nullable: true
description: Populated for capsule.* events; null if DB lookup failed.
timestamp:
type: string
format: date-time