forked from wrenn/wrenn
Start long-running processes (web servers, daemons) without blocking the
HTTP request. Leverages envd's existing background process support
(context.Background(), List, Connect, SendSignal RPCs) and wires it
through the host agent and control plane layers.
New API surface:
- POST /v1/capsules/{id}/exec with background:true → 202 {pid, tag}
- GET /v1/capsules/{id}/processes → list running processes
- DELETE /v1/capsules/{id}/processes/{selector} → kill by PID or tag
- WS /v1/capsules/{id}/processes/{selector}/stream → reconnect to output
The {selector} param auto-detects: numeric = PID, string = tag.
Tags are auto-generated as "proc-" + 8 hex chars if not provided.
2794 lines
73 KiB
YAML
2794 lines
73 KiB
YAML
openapi: "3.1.0"
|
|
info:
|
|
title: Wrenn API
|
|
description: MicroVM-based code execution platform API.
|
|
version: "0.1.0"
|
|
|
|
servers:
|
|
- url: http://localhost:8080
|
|
description: Local development
|
|
|
|
security: []
|
|
|
|
paths:
|
|
/v1/auth/signup:
|
|
post:
|
|
summary: Create a new account
|
|
operationId: signup
|
|
tags: [auth]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SignupRequest"
|
|
responses:
|
|
"201":
|
|
description: Account created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AuthResponse"
|
|
"400":
|
|
description: Invalid request (bad email, short password)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"409":
|
|
description: Email already registered
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
|
|
/v1/auth/switch-team:
|
|
post:
|
|
summary: Switch active team
|
|
operationId: switchTeam
|
|
tags: [auth]
|
|
security:
|
|
- bearerAuth: []
|
|
description: |
|
|
Re-issues a JWT scoped to a different team. The user must be a member of
|
|
the target team (verified from DB). Use the returned token for subsequent
|
|
requests to that team's resources.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [team_id]
|
|
properties:
|
|
team_id:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: New JWT issued for the target team
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AuthResponse"
|
|
"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/AuthResponse"
|
|
"401":
|
|
description: Invalid credentials
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
|
|
/v1/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"
|
|
|
|
/v1/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, and redirects to the frontend with a JWT token.
|
|
|
|
**On success:** redirects to `{OAUTH_REDIRECT_URL}/auth/{provider}/callback?token=...&user_id=...&team_id=...&email=...`
|
|
|
|
**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/api-keys:
|
|
post:
|
|
summary: Create an API key
|
|
operationId: createAPIKey
|
|
tags: [api-keys]
|
|
security:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
responses:
|
|
"204":
|
|
description: API key deleted
|
|
|
|
/v1/users/search:
|
|
get:
|
|
summary: Search users by email prefix
|
|
operationId: searchUsers
|
|
tags: [users]
|
|
security:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
description: |
|
|
Owner only. Soft-deletes the team and destroys all running/paused/starting
|
|
capsulees. 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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateCapsuleRequest"
|
|
responses:
|
|
"201":
|
|
description: Capsule created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Capsule"
|
|
"502":
|
|
description: Host agent error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
|
|
get:
|
|
summary: List capsulees for your team
|
|
operationId: listCapsules
|
|
tags: [capsules]
|
|
security:
|
|
- apiKeyAuth: []
|
|
responses:
|
|
"200":
|
|
description: List of capsulees
|
|
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: []
|
|
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/{id}:
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
|
|
get:
|
|
summary: Get capsule details
|
|
operationId: getCapsule
|
|
tags: [capsules]
|
|
security:
|
|
- apiKeyAuth: []
|
|
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: []
|
|
responses:
|
|
"204":
|
|
description: Capsule destroyed
|
|
|
|
/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: []
|
|
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: []
|
|
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: []
|
|
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: []
|
|
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: []
|
|
description: |
|
|
Resets the last_active_at timestamp for a running capsule, preventing
|
|
the auto-pause TTL from expiring. Use this as a keepalive for capsulees
|
|
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: []
|
|
- bearerAuth: []
|
|
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 capsulees, data comes from the host agent's in-memory
|
|
ring buffer. For paused capsulees, data is read from persisted
|
|
snapshots in the database. Stopped/destroyed capsulees 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: []
|
|
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:
|
|
"200":
|
|
description: Capsule paused (snapshot taken, resources released)
|
|
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: []
|
|
description: |
|
|
Restores a paused capsule from its snapshot using UFFD for lazy
|
|
memory loading. Boots a fresh Firecracker process, sets up a new
|
|
network slot, and waits for envd to become ready.
|
|
responses:
|
|
"200":
|
|
description: Capsule resumed (new VM booted from snapshot)
|
|
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: []
|
|
description: |
|
|
Pauses a running capsule, takes a full snapshot, copies the snapshot
|
|
files to the images directory as a reusable template, then destroys
|
|
the capsule. The template can be used to create new capsulees.
|
|
parameters:
|
|
- name: overwrite
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
enum: ["true"]
|
|
description: Set to "true" to overwrite an existing snapshot with the same name.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateSnapshotRequest"
|
|
responses:
|
|
"201":
|
|
description: Snapshot created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Template"
|
|
"409":
|
|
description: Name already exists or capsule not running
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
|
|
get:
|
|
summary: List templates for your team
|
|
operationId: listSnapshots
|
|
tags: [snapshots]
|
|
security:
|
|
- apiKeyAuth: []
|
|
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: []
|
|
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: []
|
|
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: []
|
|
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: []
|
|
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: []
|
|
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
|
|
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: []
|
|
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: []
|
|
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: []
|
|
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 have a 120-second inactivity timeout (reset on input/resize).
|
|
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: []
|
|
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: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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 capsulees. With `?force=true`, destroys all capsulees first.
|
|
parameters:
|
|
- name: force
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: boolean
|
|
description: If true, destroy all capsulees 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 capsulees (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:
|
|
- bearerAuth: []
|
|
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/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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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:
|
|
- bearerAuth: []
|
|
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"
|
|
|
|
components:
|
|
securitySchemes:
|
|
apiKeyAuth:
|
|
type: apiKey
|
|
in: header
|
|
name: X-API-Key
|
|
description: API key for capsule lifecycle operations. Create via POST /v1/api-keys.
|
|
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
description: JWT token from /v1/auth/login or /v1/auth/signup. Valid for 6 hours.
|
|
|
|
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
|
|
|
|
AuthResponse:
|
|
type: object
|
|
properties:
|
|
token:
|
|
type: string
|
|
description: JWT token (valid for 6 hours)
|
|
user_id:
|
|
type: string
|
|
team_id:
|
|
type: string
|
|
email:
|
|
type: string
|
|
name:
|
|
type: string
|
|
|
|
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
|
|
vcpus:
|
|
type: integer
|
|
default: 1
|
|
memory_mb:
|
|
type: integer
|
|
default: 512
|
|
timeout_sec:
|
|
type: integer
|
|
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.
|
|
|
|
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
|
|
sampled_at:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
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, paused, hibernated, stopped, missing, error]
|
|
template:
|
|
type: string
|
|
vcpus:
|
|
type: integer
|
|
memory_mb:
|
|
type: integer
|
|
timeout_sec:
|
|
type: integer
|
|
guest_ip:
|
|
type: string
|
|
host_ip:
|
|
type: string
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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 capsulees 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 capsulees 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"
|
|
|
|
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 Firecracker 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.created
|
|
- capsule.running
|
|
- capsule.paused
|
|
- capsule.destroyed
|
|
- template.snapshot.created
|
|
- template.snapshot.deleted
|
|
- 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.created
|
|
- capsule.running
|
|
- capsule.paused
|
|
- capsule.destroyed
|
|
- template.snapshot.created
|
|
- template.snapshot.deleted
|
|
- 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.
|
|
|
|
Error:
|
|
type: object
|
|
properties:
|
|
error:
|
|
type: object
|
|
properties:
|
|
code:
|
|
type: string
|
|
message:
|
|
type: string
|