HTTP API endpoints
Every public Shardflux HTTP API operation by resource: method, path, authentication, parameters and request fields, from openapi.json.
Every operation of the public HTTP API, generated from openapi.json (OpenAPI 3.1.0, API version 0.1.0). Base URL: https://api.shardflux.dev. Send a project API key on every request: Authorization: Bearer sfk_<key_id>_<secret>. Conventions (idempotency, pagination, long-running operations) are on the HTTP API page. Error responses use the ErrorBody schema; the codes are listed under Errors.
Workspace tools (exec, PTY, processes, files, git, browser) are served by the workspace’s cell at its cell_endpoint, with a token from POST /v1/workspaces/{workspace_id}/tool-tokens; they are not listed here.
Principal
The API key making the request: its organization, project and tool permissions.
GET /v1/me
The authenticated principal.
Authentication: API key
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
Organizations
The organization an API key belongs to.
GET /v1/organizations
Organizations visible to the principal.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}
Get an organization.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
Projects
The project an API key belongs to.
GET /v1/organizations/{organization_id}/projects
List projects.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/projects/{project_id}
Get a project.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/projects/{project_id}/retention
Get project idle deletion default.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
PUT /v1/projects/{project_id}/retention
Set or clear project idle deletion default.
Policy {delete_after_idle_days: 1..3650, labels?: exact-match map}, or null to clear. Applies to persistent workspaces with no own policy. All labels must match. Owner/admin/member role or own project API key required.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
Type: object or null.
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
Workspaces
Open workspaces by key, and suspend, resume, snapshot, fork, reset or close them.
POST /v1/workspaces/open
Open a workspace by key (create on first use, reconnect or resume afterwards).
like names a live workspace ID or key in the caller’s project; a new key inherits its exact template version, caps, inputs, secrets, volumes, idle policy and agent options on a fresh disk. Explicit caps, inputs, secrets, volumes and policy override the inherited values. volumes supplied replaces the set, omitted retains it. Replacement returns 202 with one normal volume_attach/volume_detach operation at a time; after it succeeds repeat open with the same set and a fresh Idempotency-Key until ready. SDKs do this when waiting. New keys resolve template to its latest published version; reopening never changes or resets the workspace (the response reports the template version actually used). 200 when the workspace is already running and ready (with cell_endpoint and a tool token); otherwise 202 with the operation to poll. A running workspace whose startup failed (startup.state failed) is not ready: the open is 202 with an open operation (input.startup_retry) that runs the failed step again. Concurrent opens of one key share one workspace and one operation. secrets (optional) binds secret names injected into every exec/PTY start: it sets the binding of a new key and replaces it on an existing key (omitted = unchanged); an unknown or unusable name is 422 details.reason secret_not_available with details.names (nothing is created or changed). The template version’s secret inputs join the binding on create and whenever secrets is given; a required one this workspace may not use is 422 input_required (details.kind secret); a bound name equal to a template env key or text input is 422 env_collision (details.name). inputs (optional): the version’s text inputs {NAME: string}; stored on create (else the declared default) and replaced on an existing key (omitted = unchanged); 422 input_unknown, input_invalid or input_required (details.names). lifetime: omitted = the version’s default (else persistent); session workspaces are discarded when the session ends (close(), idle timeout), after which the key opens a NEW workspace; reopening a live key with another lifetime is 409 lifetime_mismatch. A new workspace is layered when its version supports it and layered opens are enabled (disk_layout). Errors: 404 template/key outside scope, 402 entitlement_required, 402 card_required (no card on file: details.add_card_url; not retryable until one is saved), 403 quota_exceeded (details.limit retained_state), 429 quota_exceeded (details.limit concurrent_workspaces; see below), 409 operation in progress, key whose deletion is still in progress (workspace_deleted) or lifetime_mismatch, 422 reserved_key_prefix (keys starting with sf:). Supports Idempotency-Key. Held open: with Prefer: wait=<seconds> (at most 20) an open whose outcome is an operation is held until the operation is terminal or the wait elapses; success answers 200 with the running workspace, the succeeded operation and a tool token (Preference-Applied: wait=<seconds>), anything else 202 with the fresh operation. Without Preference-Applied the server did not wait: poll the operation. mode: omitted = processful for a new key and the stored mode for an existing one; file_first creates a workspace whose state is a versioned file tree with no VM between executions: it is ready at once (200 with a tool token, observed_state running, tree_revision 0, no operation), needs a layered template version (409 layout_unsupported otherwise) and is persistent (lifetime: session or idle_policy with it are 422 not_supported_for_mode); each open of a file-first key re-resolves the size of its execution VMs from caps, the template and the plan. 422 mode_not_available while the deployment does not offer file-first workspaces; reopening a key with another mode is 409 mode_mismatch. tool_token is null when the principal’s tool-token rate limit is used up: the open still succeeds, and the client fetches a token from POST /workspaces/{id}/tool-tokens. Plan concurrency: a start the plan’s concurrent_workspaces limit refuses is queued on the request (up to 10 s) and admitted as soon as one of the organization’s workspaces frees; if none does, 429 quota_exceeded with Retry-After: 2 (retryable; details limit concurrent_workspaces, limit_value, current, retry_after_seconds, plan). Nothing was created: retry after Retry-After, with the same Idempotency-Key or a new one. With Prefer: wait the queued time counts against the wait. Memory: the workspace holds caps.memory_mib_held while idle and grows to caps.memory_mib; 422 validation_failed details.field caps.memory_mib_held reason exceeds_memory_mib (held above caps.memory_mib); details.field caps.allocation_mode reason not_supported_for_mode (elastic with file_first).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
prefer |
header | string | no | RFC 7240 preference, e.g. wait=20 (held open). |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
key |
string | yes | Stable workspace key, unique per organization (e.g. ${customerId}/${projectId}). |
labels |
object | no | Searchable workspace metadata; not secrets. At most 50 labels. |
template |
string | no | Template slug or slug@version. A slug uses the latest published version; an exact reference selects that published, unarchived version for a new key. |
like |
string | no | Stable workspace key, unique per organization (e.g. ${customerId}/${projectId}). |
volumes |
object | no | Guest mount path → project volume name or {name, mode}. Supplied replaces the set; omitted retains it. Volumes must be available. |
caps |
object | no | Optional user caps; the ceiling is min(template, cap, plan). Absent fields add no restriction. Given caps replace the stored ones (a reopen keeps the held floor unless memory_mib_held is given). |
project_id |
string (uuid) | no | UUIDv7, lowercase canonical form. |
agent_label |
string | no | Attribution label; one agent session per (workspace, principal, label). |
tools |
array of string (one of 7) | no | |
secrets |
array of string | no | Secret names bound to the workspace (max 50, unique): injected as environment variables into every exec and PTY start (terminal sessions included), together with the call’s own secret_refs. Each must name a live secret this workspace may use (its project, its id, and allowed_tools including exec and pty), else 422 details.reason secret_not_available with details.names. |
inputs |
object | no | Open-time inputs of the template version: {NAME: string} for its declared text inputs. A new workspace stores each given value, else the declared default; on an existing key inputs replaces them all (omitted = unchanged). Secret inputs are not passed here: they bind the stored secret of the same name. 422 input_unknown (undeclared name, details.names), input_invalid (a secret input, a non-string, or a value over 4096 bytes or with CR, LF or NUL; details.names), input_required (details {names, kind}). |
lifetime |
WorkspaceLifetime | no | persistent: kept until deleted (default). session: discarded when the session ends (close(), idle timeout or draft discard;). |
idle_policy |
string | no | adaptive (the learned timeout, default), never, or fixed:<seconds> (60..604800). |
retention |
object | no | |
mode |
WorkspaceMode | no | processful (default): one VM keeps processes, memory and files between calls; it can be suspended, resumed and forked. file_first: the state is a versioned file tree (tree_revision); there is no VM between executions, each exec runs in a fresh VM and publishes the changed files as the next revision, nothing but files survives an execution. A file-first workspace is ready (running) from creation and is never suspended; suspend, resume, snapshot, fork, reset, save-as-template, volumes and idle policies are 409 not_supported_for_mode. Immutable. |
computer_use |
boolean or null | no | : the workspace’s computer use switch (null follows the template); set on create, replaced on an existing key when given. |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/workspaces
List workspaces (API keys: their project; users: their organizations).
By default only persistent standard workspaces; lifetime and purpose (each also any) show sessions, drafts and test instances. Ended sessions are tombstones: add include_deleted=true.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
organization_id |
query | string (uuid) | no | UUIDv7, lowercase canonical form. |
project_id |
query | string (uuid) | no | UUIDv7, lowercase canonical form. |
state |
query | string (one of 11) | no | Observed state filter. |
desired_state |
query | "running" or "suspended" or "deleted" | no | |
key_prefix |
query | string | no | |
labels |
query | string | no | JSON object of exact label matches; all pairs must match. |
include_deleted |
query | boolean | no | |
lifetime |
query | "persistent" or "session" or "any" | no | persistent (default), session or any. Key lookups pass any. |
purpose |
query | "standard" or "template_draft" or "template_test" or "any" | no | standard (default), template_draft, template_test or any. Key lookups pass any. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/workspaces
List an organization’s workspaces (API keys see only their project).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
project_id |
query | string (uuid) | no | UUIDv7, lowercase canonical form. |
state |
query | string (one of 11) | no | Observed state filter. |
desired_state |
query | "running" or "suspended" or "deleted" | no | |
key_prefix |
query | string | no | |
labels |
query | string | no | JSON object of exact label matches; all pairs must match. |
include_deleted |
query | boolean | no | |
lifetime |
query | "persistent" or "session" or "any" | no | persistent (default), session or any. Key lookups pass any. |
purpose |
query | "standard" or "template_draft" or "template_test" or "any" | no | standard (default), template_draft, template_test or any. Key lookups pass any. |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/workspaces/{workspace_id}
Get a workspace: desired/observed state, cell, template version, caps/ceilings, grants, active operation, pending reason.
Tombstoned workspaces stay readable (deleted_at set) until final cleanup.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 Workspace · 4XX ErrorBody · 5XX ErrorBody
DELETE /v1/workspaces/{workspace_id}
Delete a workspace (tombstone now, storage cleanup by the cell).
Sets desired_state=deleted and deleted_at, revokes tool access immediately (workspace revocation watermark, agent sessions revoked) and creates a delete operation for the cell. A file-first workspace’s tree revisions are deleted with the tombstone and the cell deletes its stored files. Repeating returns the same operation. After deletion finishes (observed_state deleted), the key can open a new workspace ID; old IDs and tokens remain deleted. Supports Idempotency-Key. Held: with Prefer: wait=<seconds> (at most 20) the response is held until the operation is terminal or the wait elapses; succeeded answers 200 with the workspace and the succeeded operation (Preference-Applied: wait=<seconds>), anything else 202 with the fresh operation. Without Preference-Applied the server did not wait: poll the operation.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
prefer |
header | string | no | RFC 7240 preference, e.g. wait=20 (held lifecycle call). |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/workspaces/{workspace_id}/inputs
The workspace’s text inputs.
The text inputs the workspace was opened with (the given value, else the declared default). Every exec, PTY, start command and service gets them as environment variables, above the template env and below the call’s own env. Secret inputs are bound secrets (GET …/secrets). Set on create; replaced by an open of the key with inputs. Deleted workspaces stay readable.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 WorkspaceInputs · 4XX ErrorBody · 5XX ErrorBody
PUT /v1/workspaces/{workspace_id}/labels
Replace workspace labels.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
labels |
object | yes | Searchable workspace metadata; not secrets. At most 50 labels. |
Responses: 200 Workspace · 4XX ErrorBody · 5XX ErrorBody
PUT /v1/workspaces/{workspace_id}/retention
Set or clear idle deletion retention.
JSON policy {delete_after_idle_days: 1..3650}, or null to follow the project default. Persistent only (422 retention_requires_persistent); deleted workspace 409 workspace_deleted. Returns the workspace with projected deletion time.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
Type: object or null.
Responses: 200 Workspace · 4XX ErrorBody · 5XX ErrorBody
PUT /v1/workspaces/{workspace_id}/idle-policy
Set or clear the workspace idle policy (automatic suspend).
idle_policy: adaptive (the learned timeout), never, or fixed:<seconds> (60..604800); null clears it so the template default (else adaptive) applies. Work signals always win: a running command, an attached session or a keepalive keeps the workspace running. Applies from the idle loop’s next evaluation. 409 session_lifetime for session workspaces (they end after their idle timeout), 409 not_supported_for_mode for file-first workspaces (never suspended), 409 workspace_deleted. Returns the workspace.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
idle_policy |
string or null | yes |
Responses: 200 Workspace · 4XX ErrorBody · 5XX ErrorBody
PUT /v1/workspaces/{workspace_id}/computer-use
Switch computer use on or off for the workspace.
enabled: true or false overrides the template’s switch; null follows it. While computer use is on, tool tokens carry the computer tool and the workspace desktop starts on the first computer call. Takes effect on the next tool token. 409 computer_use_unavailable when the workspace’s template version cannot run a desktop, 409 not_supported_for_mode for file-first workspaces, 409 workspace_deleted. Returns the switch.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
enabled |
boolean or null | yes | true or false overrides the template; null follows it. |
Responses: 200 ComputerUse · 4XX ErrorBody · 5XX ErrorBody
POST /v1/workspaces/{workspace_id}/suspend
Suspend a running workspace (durable full-state checkpoint; a session workspace is 409 session_lifetime).
Creates a durable operation executed by the cell (Phase 8); poll GET /operations/{id}. A file-first workspace is 409 not_supported_for_mode. Supports Idempotency-Key. Held: with Prefer: wait=<seconds> (at most 20) the response is held until the operation is terminal or the wait elapses; succeeded answers 200 with the workspace and the succeeded operation (Preference-Applied: wait=<seconds>), anything else 202 with the fresh operation. Without Preference-Applied the server did not wait: poll the operation.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
prefer |
header | string | no | RFC 7240 preference, e.g. wait=20 (held lifecycle call). |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/workspaces/{workspace_id}/resume
Resume a suspended workspace (admitted like a start).
Creates a resume operation for a suspended workspace, or an open recovery for a failed workspace (same ID and disk, no reset), or returns the active resume/open (concurrent wakes join one operation); 202 with it: poll GET /operations/{id}. Errors: 409 already_running or not_suspended, operation_in_progress (a suspend or another operation is active; details.active_operation_id), workspace_deleted, not_supported_for_mode (file-first); 402/403/429 as for open (admitted like a start). Supports Idempotency-Key. Held resume: with Prefer: wait=<seconds> (at most 20) the response is held until the operation is terminal or the wait elapses, exactly like a held open; success answers 200 with the running workspace, the succeeded operation, cell_endpoint and a tool token for agent_label/tools (minted while the restore is in flight, at the new ownership epoch), Preference-Applied: wait=<seconds>; anything else is 202 with the fresh operation. With the preference a workspace that is already running answers 200 at once (operation null, a token) instead of 409 already_running. Without Preference-Applied the server did not wait: poll the operation. tools beyond the principal’s tool permissions are 403 (nothing is created); a token the API cannot issue otherwise is tool_token: null. Plan concurrency: a start the plan’s concurrent_workspaces limit refuses is queued on the request (up to 10 s) and admitted as soon as one of the organization’s workspaces frees; if none does, 429 quota_exceeded with Retry-After: 2 (retryable; details limit concurrent_workspaces, limit_value, current, retry_after_seconds, plan). Nothing was created: retry after Retry-After, with the same Idempotency-Key or a new one. With Prefer: wait the queued time counts against the wait.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
prefer |
header | string | no | RFC 7240 preference, e.g. wait=20 (held resume). |
Request body (application/json, optional)
| Field | Type | Required | Description |
|---|---|---|---|
agent_label |
string | no | Attribution label; one agent session per (workspace, principal, label). |
tools |
array of string (one of 7) | no |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/workspaces/{workspace_id}/snapshot
Snapshot a running or suspended workspace.
Creates a durable operation executed by the cell (Phase 8); poll GET /operations/{id}. A file-first workspace is 409 not_supported_for_mode. Supports Idempotency-Key. Held: with Prefer: wait=<seconds> (at most 20) the response is held until the operation is terminal or the wait elapses; succeeded answers 200 with the workspace and the succeeded operation (Preference-Applied: wait=<seconds>), anything else 202 with the fresh operation. Without Preference-Applied the server did not wait: poll the operation.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
prefer |
header | string | no | RFC 7240 preference, e.g. wait=20 (held lifecycle call). |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
label |
string | no |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/workspaces/{workspace_id}/upgrade
Upgrade a workspace in place, preserving its disk.
Opt-in cold start: preserves files, installed package files and home; drops memory and processes. now boots immediately; next_resume schedules a pinned target. Layered disks refresh the template base and guest; legacy disks upgrade memory only. Returns an upgrade operation. Errors: 409 already_current, legacy_disk_layout, upgrade_base_unavailable, not_upgradable, operation_in_progress, workspace_deleted; 422 not_supported_for_mode; now is admitted like a start (402/403/429 as for open). Supports Idempotency-Key. Plan concurrency: a start the plan’s concurrent_workspaces limit refuses is queued on the request (up to 10 s) and admitted as soon as one of the organization’s workspaces frees; if none does, 429 quota_exceeded with Retry-After: 2 (retryable; details limit concurrent_workspaces, limit_value, current, retry_after_seconds, plan). Nothing was created: retry after Retry-After, with the same Idempotency-Key or a new one. With Prefer: wait the queued time counts against the wait.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
at |
"now" or "next_resume" | no |
Responses: 202 · 4XX ErrorBody · 5XX ErrorBody
PATCH /v1/workspaces/{workspace_id}/caps
Resize a workspace: memory, CPU and disk, running or suspended.
Changes the caps of any workspace, running or suspended, without restarting or forking it. Each given field replaces the stored one (omitted fields keep theirs); every later start uses the new caps. Each value is bounded like caps at open: by the plan’s per-workspace maximum and the template’s limit (limit_reason plan or template). A running workspace changes live where it can (memory plugged or unplugged, CPU within its boot vCPUs, disk grown online); a suspended one gets the change right after its next resume, before the first call; what cannot apply now says applies_at: next_start and applies at the next start. The request is held until the resize finishes, up to Prefer: wait=<seconds> (default 20, at most 55): 200 ResizeResult (per resource: applies_at now | resume | next_start, requested, target, previous and applied values, limit_reason, reason) with the workspace caps; otherwise 202 with the resize operation, whose result carries the same per-resource fields when it succeeds. A resize that fails answers with its error. Errors (nothing changes): 409 conflict reason resize_not_available (not enabled for the organization), workspace_deleted, operation_in_progress (another lifecycle operation is active; details.active_operation_id, retryable); 422 validation_failed reason not_supported_for_mode (file-first workspace), shrink_not_supported (disk_gib below the current disk; details.current_disk_gib), exceeds_memory_mib (memory_mib_held above the maximum); 402 as for a start (no plan, new starts on hold). Owners, admins and members, and API keys with a tool permission. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
prefer |
header | string | no | RFC 7240 preference, e.g. wait=30 (at most 55; default 20). |
Request body (application/json, required, ResizeWorkspaceBody)
| Field | Type | Required | Description |
|---|---|---|---|
memory_mib |
integer | no | Memory (MiB): the most the workspace can use. Bounded by the plan and the template (limit_reason). |
memory_mib_held |
integer | no | The memory held while idle (MiB), at most the maximum. |
allocation_mode |
"fixed" or "elastic" | no | Deprecated; accepted and ignored. Every workspace is elastic: it holds memory_mib_held while idle and grows to memory_mib as its processes use memory. |
cpu_millis |
integer | no | CPU (millicores). Bounded by the plan and the template. |
disk_gib |
integer | no | Disk (GiB). Grow only. With at_least, a smaller or equal disk is unchanged. Bounded by the plan and the template. |
at_least |
boolean | no | Only increase each named numeric resource; values at or below its current configured size leave it unchanged. |
Responses: 200 ResizeResult · 202 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/workspaces/{workspace_id}/suspend-when-idle
Suspend the workspace if it stays idle for after_seconds from now (e.g. at the end of an agent turn).
Records a deferred suspend for the cell’s idle loop: once the workspace has been idle for after_seconds (0..3600; 0 = as soon as it is idle), it is suspended (within a few seconds of activity flush grace), at not_before (= now + after_seconds) at the earliest. A tool call after the request (the next turn) or a resume cancels it for good. Other work only defers it: a command still running, an attached exec/PTY stream or a keepalive postpones the suspend until after_seconds after it ends. It applies under every idle policy (never included) and only ever shortens the policy’s wait; a policy that suspends sooner still does. Repeating replaces the pending request (the new requested_at counts); DELETE cancels it; idle.suspend_request on the workspace shows it. 202 {workspace, operation: null, suspend_request}; when a suspend is already in progress, 202 {workspace, operation: that suspend, suspend_request: null} and nothing is recorded. The suspend, when it happens, is a system suspend operation with input.reason requested_after_idle (input.requested_at, input.after_seconds). Errors: 409 session_lifetime, workspace_deleted, operation_in_progress (another lifecycle operation is active), not_running, not_supported_for_mode (file-first). Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
after_seconds |
integer | yes | Seconds from now the workspace must stay idle before it is suspended (0..3600; 0 = as soon as it is idle). |
Responses: 202 · 4XX ErrorBody · 5XX ErrorBody
DELETE /v1/workspaces/{workspace_id}/suspend-when-idle
Cancel a pending suspend-when-idle request.
Idempotent: 200 with the workspace whether or not a request was pending, in any workspace state (idle.suspend_request is null afterwards). A suspend the request already started is not undone: it shows as the workspace’s active_operation; resume or open the workspace instead.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 Workspace · 4XX ErrorBody · 5XX ErrorBody
POST /v1/workspaces/{workspace_id}/fork
Fork a workspace into a new key (independent copy of its committed state).
Creates the target workspace (same template version and disk layout) and a fork operation on it, admitted like a start. Caps default to the source’s. lifetime is the fork’s own (default persistent): forking a session keeps its state. A file-first source is 409 not_supported_for_mode. Supports Idempotency-Key. Caps (and the memory allocation mode) default to the source’s. Held fork: with Prefer: wait=<seconds> (at most 20), holds through the terminal operation. Success returns 200 with the running target, succeeded operation and a final-epoch tool token for agent_label/tools. Otherwise returns 202 with the fresh operation. Preference-Applied confirms the wait; without it, poll as usual. A token that could not be issued is null; fetch it from the target tool-token route. Errors: 402/403/429 as for open (admitted like a start). Plan concurrency: a start the plan’s concurrent_workspaces limit refuses is queued on the request (up to 10 s) and admitted as soon as one of the organization’s workspaces frees; if none does, 429 quota_exceeded with Retry-After: 2 (retryable; details limit concurrent_workspaces, limit_value, current, retry_after_seconds, plan). Nothing was created: retry after Retry-After, with the same Idempotency-Key or a new one. With Prefer: wait the queued time counts against the wait. Memory: the workspace holds caps.memory_mib_held while idle and grows to caps.memory_mib; 422 validation_failed details.field caps.memory_mib_held reason exceeds_memory_mib (held above caps.memory_mib); details.field caps.allocation_mode reason not_supported_for_mode (elastic with file_first).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
prefer |
header | string | no |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
key |
string | yes | Stable workspace key, unique per organization (e.g. ${customerId}/${projectId}). |
caps |
object | no | Optional user caps; the ceiling is min(template, cap, plan). Absent fields add no restriction. Given caps replace the stored ones (a reopen keeps the held floor unless memory_mib_held is given). |
lifetime |
WorkspaceLifetime | no | persistent: kept until deleted (default). session: discarded when the session ends (close(), idle timeout or draft discard;). |
labels |
object | no | Searchable workspace metadata; not secrets. At most 50 labels. |
agent_label |
string | no | Attribution label; one agent session per (workspace, principal, label). |
tools |
array of string (one of 7) | no |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/workspaces/{workspace_id}/close
Close a session workspace (ends the session: the workspace is deleted).
Sessions only (409 not_session for a persistent workspace; the SDK then only closes local streams). Ends the session exactly like a delete (VM stopped without a snapshot, layer and checkpoints released) with ended_reason closed; the key then opens a NEW workspace. 202 with the delete operation (input.reason session_closed). Idempotent: repeating returns the active or last delete operation. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 202 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/workspaces/{workspace_id}/reset
Reset a layered workspace to its template (wipes the workspace layer).
Keeps the key, id, template version, caps, secret bindings, volume attachments and history; wipes every change. Running: restarted on a blank layer (processes are gone, epoch + 1, old tool tokens get 409 stale_epoch). Suspended: stays suspended; the next resume boots blank. The previous checkpoint stays restorable for 7 days (result.recovery_checkpoint_id). 202 with the reset operation. Errors: 422 confirm_destructive_required, 409 legacy_disk_layout, not_resettable, operation_in_progress, not_supported_for_mode (file-first). Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required, ResetWorkspaceBody)
| Field | Type | Required | Description |
|---|---|---|---|
confirm_destructive |
boolean | no | Must be true: reset wipes every change in the workspace layer (422 confirm_destructive_required otherwise). |
Responses: 202 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/workspaces/{workspace_id}/save-as-template
Save a layered workspace as the next version of an organization template.
Everything in the workspace becomes template content (its whole filesystem, minus the sf-scrub.v1 list, the contents of /proc, /sys, /dev, /run and /tmp, and shared-volume contents), stored as one new org layer on the workspace’s template chain. A running workspace is captured briefly (operation, layer_snapshot); a suspended one uses its current checkpoint; checkpoint_id saves a committed checkpoint instead. 202 SaveAsTemplateResponse: poll the build. Owners/admins and API keys with a tool permission (403 otherwise). Immutable paths: the new version declares the target template’s open version’s immutable paths (else the workspace’s version’s); their content comes from that template’s newest published version, not from the workspace (a draft publish keeps the draft’s own content). Errors: 409 legacy_disk_layout, workspace_not_running, operation_in_progress, not_supported_for_mode (file-first), template_archived, 422 platform_template_slug, invalid_defaults, too_many_acknowledged_findings, invalid_path, immutable_paths_unsupported_base (the workspace’s base has no guest agent with immutable_paths.v1; details.base), 429 rate_limited (template_build_queue_full: the plan’s queued_template_builds (10 by default) already queued past its concurrent_template_builds; retryable, Retry-After; a build past the plan’s slots is accepted and waits, its queue gives the position and expected wait). Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required, SaveAsTemplateBody)
| Field | Type | Required | Description |
|---|---|---|---|
template_slug |
string | yes | Organization template to save into (created when absent; platform slugs are refused). |
display_name |
string | no | Template name when this save creates the template. |
description |
string | no | The version description (default: the source version’s). |
defaults |
object | no | Defaults of the new version (omitted fields: persistent lifetime, platform idle timeout, no limits). |
checkpoint_id |
string (uuid) | no | A committed checkpoint of this workspace to save instead of its current state. |
auto_publish |
boolean | no | |
acknowledged_scan_findings |
array of string | no | Up to 200 absolute paths the credential scan may report without failing the build (recorded in the manifest). |
settings |
TemplateSettingsInput | no | What a workspace of the version gets when it opens (). Omitted fields are empty (recipes do not carry settings forward). |
Responses: 202 SaveAsTemplateResponse · 4XX ErrorBody · 5XX ErrorBody
PUT /v1/workspaces/{workspace_id}/ports/{port}
Expose a port of the workspace at its own HTTPS URL.
Serves TCP port inside the workspace at https://<port>-<handle>.<ingress domain>. The port is private: every request needs a port token (POST …/tokens), a signed link (POST …/links) or the port’s callback URL (POST …/callback). A request to a suspended workspace resumes it and is held until it runs; a parked one wakes inside the request. 201 when exposed now, 200 when it already was (unchanged: its tokens, links and callback URL stay valid). The handle is the workspace’s for good (kept when ports are closed and exposed again, across suspend, resume and reset; a fork gets none). At most the plan’s exposed ports (10 by default): 409 conflict details.reason port_limit (details.limit). Errors: 403 forbidden details.reason inbound_ports_not_available (inbound ports are not enabled for the organization), 409 conflict not_supported_for_mode (file-first workspace) or workspace_deleted, 422 validation_failed for a port outside 1-65535 (details.reason port_reserved for 61001, the workspace desktop’s control port).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
port |
path | integer | yes | TCP port inside the workspace (1-65535). |
Responses: 200 Port · 201 Port · 4XX ErrorBody · 5XX ErrorBody
DELETE /v1/workspaces/{workspace_id}/ports/{port}
Close an exposed port.
204, also when the port was not exposed. Its tokens, links and callback URL stop working at once (the gateway caches for at most 5 s), and stay revoked if the port is exposed again. The workspace keeps its URL handle. Errors: 403 forbidden details.reason inbound_ports_not_available (inbound ports are not enabled for the organization), 409 conflict not_supported_for_mode (file-first workspace) or workspace_deleted, 422 validation_failed for a port outside 1-65535 (details.reason port_reserved for 61001, the workspace desktop’s control port).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
port |
path | integer | yes | TCP port inside the workspace (1-65535). |
Responses: 204 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/workspaces/{workspace_id}/ports
List the workspace’s exposed ports.
Ordered by port. callback is set when the port has a callback URL (its secret is never shown again). Errors: 403 forbidden details.reason inbound_ports_not_available (inbound ports are not enabled for the organization), 409 conflict not_supported_for_mode (file-first workspace) or workspace_deleted, 422 validation_failed for a port outside 1-65535 (details.reason port_reserved for 61001, the workspace desktop’s control port).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/workspaces/{workspace_id}/ports/{port}/tokens
Mint a port token for an exposed port.
A bearer token for requests to the port’s URL: Authorization: Bearer <token> (or X-Shardflux-Token: <token> when the app uses Authorization itself). sfp_ + an ES256 JWS (claims iss, aud shardflux-ports, sub, pty, org, prj, ws, port, pxid, use bearer, iat, exp, jti; keys at GET /v1/.well-known/tool-token-keys). Revoked by closing the port, deleting the workspace, revoking the API key that minted it, or the minting member’s removal or role change. 404 not_found details.reason port_not_exposed. Errors: 403 forbidden details.reason inbound_ports_not_available (inbound ports are not enabled for the organization), 409 conflict not_supported_for_mode (file-first workspace) or workspace_deleted, 422 validation_failed for a port outside 1-65535 (details.reason port_reserved for 61001, the workspace desktop’s control port).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
port |
path | integer | yes | TCP port inside the workspace (1-65535). |
Request body (application/json, optional)
| Field | Type | Required | Description |
|---|---|---|---|
ttl_seconds |
integer | no | Lifetime in seconds (60-86400, default 3600). |
Responses: 201 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/workspaces/{workspace_id}/ports/{port}/links
Create a signed link that opens an exposed port in a browser.
Opt-in embed: {origins: ["https://app.example.com"]} creates a partitioned browser session with a frame-ancestors allowlist. Opening the link sets a session cookie for the port’s host (HttpOnly, Secure, until expires_at) and redirects to path, so a person can use the app in a browser without a header. Share it like a password: anyone with the link can open the port until it expires. Revoked like a port token (closing the port, deleting the workspace, revoking the minting API key). 422 validation_failed details.field path for a path that does not start with one / or has characters other than printable ASCII. 404 not_found details.reason port_not_exposed. Errors: 403 forbidden details.reason inbound_ports_not_available (inbound ports are not enabled for the organization), 409 conflict not_supported_for_mode (file-first workspace) or workspace_deleted, 422 validation_failed for a port outside 1-65535 (details.reason port_reserved for 61001, the workspace desktop’s control port).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
port |
path | integer | yes | TCP port inside the workspace (1-65535). |
Request body (application/json, optional)
| Field | Type | Required | Description |
|---|---|---|---|
ttl_seconds |
integer | no | Lifetime of the link and of the browser session it starts (60-604800 s, default 86400). |
embed |
object | no | Embed in these 1–10 canonical HTTPS origins, at most 2048 total origin characters. |
path |
string | no | Where the link lands (default /): a path starting with one /, printable ASCII, query allowed. |
Responses: 201 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/workspaces/{workspace_id}/ports/{port}/callback
Create or replace the port’s callback URL (webhooks, OAuth redirects).
A URL with a secret in its path that reaches the port without a token header: register <url><your path> with GitHub, Slack, Stripe or an OAuth provider; a request to /__shardflux/callback/<secret>/<rest> reaches /<rest> (query kept) and wakes the workspace like any request. Creating it again replaces it (the previous URL stops working). The secret is shown only in this response (stored as a SHA-256 hash). Revoke it with DELETE, by closing the port, or by deleting the workspace; it also stops waking the workspace when its creator can no longer manage the workspace. 404 not_found details.reason port_not_exposed. Errors: 403 forbidden details.reason inbound_ports_not_available (inbound ports are not enabled for the organization), 409 conflict not_supported_for_mode (file-first workspace) or workspace_deleted, 422 validation_failed for a port outside 1-65535 (details.reason port_reserved for 61001, the workspace desktop’s control port).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
port |
path | integer | yes | TCP port inside the workspace (1-65535). |
Responses: 201 · 4XX ErrorBody · 5XX ErrorBody
DELETE /v1/workspaces/{workspace_id}/ports/{port}/callback
Revoke the port’s callback URL.
204, also when the port has no callback URL. The URL stops working at once (the gateway caches for at most 5 s). 404 not_found details.reason port_not_exposed. Errors: 403 forbidden details.reason inbound_ports_not_available (inbound ports are not enabled for the organization), 409 conflict not_supported_for_mode (file-first workspace) or workspace_deleted, 422 validation_failed for a port outside 1-65535 (details.reason port_reserved for 61001, the workspace desktop’s control port).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
port |
path | integer | yes | TCP port inside the workspace (1-65535). |
Responses: 204 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/workspaces/{workspace_id}/secrets
Secret names bound to a workspace, with per-name status (never values).
Bound secrets are injected into every exec and PTY start of the workspace together with the call’s secret_refs. status: available | not_allowed | deleted. Deleted workspaces stay readable.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
PUT /v1/workspaces/{workspace_id}/secrets
Replace the secret names bound to a workspace.
Replaces the whole binding (names: [] clears it); applies from the next exec/PTY start (running processes keep their environment). Every name must be a live secret this workspace may use (its project, its id, and allowed_tools including exec and pty), else 422 details.reason secret_not_available with details.names and nothing changes. A name equal to a key of the template version’s env or one of its text inputs is 422 env_collision (details.name;). Exactly the given names are bound (the version’s secret inputs join the binding only through open). Audited when the binding changes. 409 workspace_deleted.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
names |
array of string | yes | Secret names bound to the workspace (max 50, unique): injected as environment variables into every exec and PTY start (terminal sessions included), together with the call’s own secret_refs. Each must name a live secret this workspace may use (its project, its id, and allowed_tools including exec and pty), else 422 details.reason secret_not_available with details.names. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
Operations
Lifecycle operations (open, suspend, resume, fork, ...) and their progress.
GET /v1/operations/{operation_id}
Poll an asynchronous operation.
Stays readable after its workspace is tombstoned. Bounded wait: with Prefer: wait=<seconds> (at most 20) and an operation that is not terminal, the response is held until its state or state_reason changes or the wait elapses, then carries the fresh operation and Preference-Applied: wait=<seconds>. Without Preference-Applied the server did not wait: poll with backoff.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
operation_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
prefer |
header | string | no | RFC 7240 preference, e.g. wait=20. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/operations/{operation_id}/progress
Read live suspension progress without waking the workspace.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
operation_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/operations/{operation_id}/cancel
Cancel suspension preparation before its capture cut.
Only a host-acknowledged pre-cut cancellation is accepted. 202 while the owning controller restores the original workspace and opens its tools; poll the same operation until canceled. After the capture cut: 409 suspend_capture_started; wait for the operation and resume. Never resets or replaces the workspace. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
operation_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/workspaces/{workspace_id}/operations
List a workspace’s operations, newest first.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
state |
query | "queued" or "capacity_pending" or "running" or "succeeded" or "failed" or "canceled" | no | |
kind |
query | string (one of 16) | no | |
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
Tool tokens
Short-lived tokens for the workspace tools (exec, PTY, files, git, browser) served at the workspace’s cell_endpoint, and agent sessions.
POST /v1/workspaces/{workspace_id}/tool-tokens
Issue a workspace tool token (ES256, <= 15 min) for the cell gateway.
Creates or reuses the agent session for (workspace, principal, agent_label) and signs a token with claims iss, aud, sub, pty, org, prj, ws, epoch, tools, sid, iat, exp, jti (verify with GET /v1/.well-known/tool-token-keys). tools must be a subset of the API key’s tool permissions (users: of their role). A suspended workspace gets a token too: the cell serves it only the reads of its disk (files read, stat, list and search, X-Served-From: disk) and answers every other call 409 workspace_not_running (wake the workspace; its resume moves the ownership epoch). 409 with the active operation when the workspace is neither running nor suspended; on 409 stale_epoch from the cell, request a new token.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
agent_label |
string | no | Attribution label; one agent session per (workspace, principal, label). |
tools |
array of string (one of 7) | no |
Responses: 201 ToolToken · 4XX ErrorBody · 5XX ErrorBody
GET /v1/workspaces/{workspace_id}/agent-sessions
List the attributed agent sessions of a workspace.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
Volumes
Persistent volumes shared between workspaces of a project.
GET /v1/projects/{project_id}/volumes
List a project’s shared volumes (optionally with the organization’s org-shared volumes).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
include_org_shared |
query | boolean | no | Also list org-shared volumes owned by other projects of the organization (attachable here). |
include_deleted |
query | boolean | no | |
project_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/projects/{project_id}/volumes
Create a volume.
202 with the volume (state creating) and its volume_create operation; the volume becomes available when the cell has created its storage (poll the operation or the volume). Quotas from the plan: limit.shared_volumes_max (live volumes per organization) and limit.shared_volume_gib_max (sum of quota_gib across the organization’s live volumes) -> 403 quota_exceeded (details.limit shared_volumes_max for count or shared_volume_gib_total for capacity; current and limit_value in GiB for capacity); a plan without them -> 402 entitlement_required (details.reason limits_missing). org_shared: true (owners/admins only) makes it attachable from every project of the organization. 409 name_in_use. kind defaults to shared; kind block requires policy.block_volumes (403 forbidden, reason block_volumes_not_enabled). Block quota_gib is the exact device size, capped by workspace_disk_gib_max; counts toward shared_volumes_max and excluded from the shared GiB total. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Unique among the project’s live volumes. |
kind |
"shared" or "block" | no | Storage kind. Block volumes require policy.block_volumes and attach exclusively to one workspace. |
quota_gib |
integer | yes | |
org_shared |
boolean | no |
Responses: 202 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/projects/{project_id}/volumes/open
Create or reuse a shared volume by name.
200 {volume} reuses a live name of the same kind, including a volume still creating; quota_gib applies only on creation (default 1 GiB). 202 {volume, operation} creates it. Different kind: 409 conflict, reason volume_kind_mismatch. Block creation requires policy.block_volumes (403 forbidden, reason block_volumes_not_enabled); quota_gib is its exact device size, at most workspace_disk_gib_max. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Unique among the project’s live volumes. |
kind |
"shared" or "block" | no | Storage kind. Block volumes require policy.block_volumes and attach exclusively to one workspace. |
quota_gib |
integer | no |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/projects/{project_id}/volumes/{volume_id}
Get a shared volume (the project’s own, or an org-shared volume of its organization).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
volume_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 Volume · 4XX ErrorBody · 5XX ErrorBody
DELETE /v1/projects/{project_id}/volumes/{volume_id}
Delete a shared volume and its data (executed by the cell).
202 with the volume (state deleting) and its volume_delete operation; deleted once the cell removed the data and the access point. Refused with 409 volume_attached (details.attachments) while it is attached, unless force=true&confirm_name=<volume name>: attachments are then detached by the delete (running guests see I/O errors on the mount). Only the owning project; org-shared volumes only by owners/admins. Idempotent (a deleting/deleted volume returns its delete operation). Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
force |
query | boolean | no | |
confirm_name |
query | string | no | |
project_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
volume_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 202 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/projects/{project_id}/volumes/{volume_id}/attachments
List a volume’s attachments (API keys: only to their project’s workspaces).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
include_detached |
query | boolean | no | |
project_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
volume_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/workspaces/{workspace_id}/volumes
List the volumes attached to a workspace.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
include_detached |
query | boolean | no | |
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/workspaces/{workspace_id}/volumes
Attach a shared volume to a workspace at mount_path (ro or rw).
202 with the attachment (state attaching) and its volume_attach operation; the cell mounts it in the running guest (or records it for the next start when the workspace has no VM) and reports attached. The volume must be available, of the same organization and of the workspace’s project unless org-shared (409 volume_not_shared). One attachment per (volume, workspace); mount paths may not nest (409 mount_path_conflict); at most 8 per workspace (403 quota_exceeded). Attach/detach share the workspace’s single active lifecycle operation: 409 operation_in_progress while another one runs. Block volumes are exclusive, including ro: 409 conflict reason volume_in_use with attachment_id (workspace_id when visible); retryable false while attached, true with retry_after_seconds 2 while detaching. New block attachments require policy.block_volumes (403 forbidden reason block_volumes_not_enabled), a layered template advertising block_volumes.v1 (409 block_volumes_unsupported_base), and slots in a running VM (409 block_volume_restart_required; upgrade at now). At most two block attachments (403 quota_exceeded, block_volume_attachments_per_workspace). Re-attaching an attached volume with the same mount_path/mode returns 200 without a new operation. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
volume_id |
string (uuid) | yes | UUIDv7, lowercase canonical form. |
mount_path |
string | yes | Absolute guest path (e.g. /mnt/data); segments of A-Z a-z 0-9 . _ - not starting with "."; system directories (/etc, /usr, /proc, /tmp, /var, ...) and nesting with another mount are refused. |
mode |
"ro" or "rw" | no |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
DELETE /v1/workspaces/{workspace_id}/volumes/{volume_id}
Detach a shared volume from a workspace.
202 with the attachment (state detaching) and its volume_detach operation; the cell unmounts it (the guest sees I/O errors on the path afterwards, never stale writes) and reports detached. Idempotent while detaching. 404 when the volume is not attached. 409 operation_in_progress while another lifecycle operation of the workspace runs. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
volume_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 202 · 4XX ErrorBody · 5XX ErrorBody
Browser profiles
Write-only browser sessions scoped to a project or workspace.
GET /v1/browser-profiles
List browser sessions (metadata only).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
query | string (uuid) | no | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
PUT /v1/browser-profiles/{name}
Store encrypted browser sessions (write-only).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name |
path | string | yes |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
scope |
"project" or object | yes | |
sites |
array of object | yes | |
project_id |
string (uuid) | no | UUIDv7, lowercase canonical form. |
Responses: 200 BrowserProfile · 4XX ErrorBody · 5XX ErrorBody
DELETE /v1/browser-profiles/{name}
Remove a browser profile or one site.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
query | string (uuid) | no | UUIDv7, lowercase canonical form. |
site |
query | string | no | |
name |
path | string | yes |
Responses: 204 · 4XX ErrorBody · 5XX ErrorBody
Templates
Published templates and their versions, files, recipes and diffs.
GET /v1/organizations/{organization_id}/templates
List the templates an organization can use (platform + its own), with the version open picks.
Archived templates only with include_archived=true. Ordered by id; cursor pagination.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
include_archived |
query | boolean | no | Include archived templates and versions. |
owner |
query | "platform" or "organization" | no | |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/templates/{slug}
Get a template by slug with its versions, compatibility, caps, installed tools and plan clamping.
The organization’s own template shadows a platform template of the same slug (like open); open_resolves_to is what open would use now. Unpublished versions appear only for owners/admins of the owning organization. 404 outside the organization.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
include_archived |
query | boolean | no | Include archived templates and versions. |
owner |
query | "platform" or "organization" | no | Pick the platform template even when an organization template shadows its slug. |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/templates
List the templates an organization can use (platform + its own), with the version open picks (the API key’s organization).
Archived templates only with include_archived=true. Ordered by id; cursor pagination.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
include_archived |
query | boolean | no | Include archived templates and versions. |
owner |
query | "platform" or "organization" | no |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/templates/{slug}
Get a template by slug with its versions, compatibility, caps, installed tools and plan clamping (the API key’s organization).
The organization’s own template shadows a platform template of the same slug (like open); open_resolves_to is what open would use now. Unpublished versions appear only for owners/admins of the owning organization. 404 outside the organization.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
include_archived |
query | boolean | no | Include archived templates and versions. |
owner |
query | "platform" or "organization" | no | Pick the platform template even when an organization template shadows its slug. |
slug |
path | string | yes |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
PUT /v1/organizations/{organization_id}/templates/{slug}/computer-use
Switch computer use on or off for the workspaces of an organization template.
Every workspace of the template follows it unless the workspace sets its own value; takes effect on their next tool token. Owners and admins (sessions) or API keys of the organization with a tool permission. 403 for platform templates (switch a workspace on instead), 404 outside the organization.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
enabled |
boolean | yes |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
PUT /v1/templates/{slug}/computer-use
Switch computer use on or off for the workspaces of an organization template (the API key’s organization).
Every workspace of the template follows it unless the workspace sets its own value; takes effect on their next tool token. Owners and admins (sessions) or API keys of the organization with a tool permission. 403 for platform templates (switch a workspace on instead), 404 outside the organization.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
slug |
path | string | yes |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
enabled |
boolean | yes |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/templates/{slug}/versions/{version}/files
List one directory of a template version’s file tree.
Entries directly inside path (default /), sorted by name bytes, keyset-paginated. The tree covers the whole filesystem (only the contents of /proc, /sys, /dev, /run and /tmp are left out). 409 file_list_unavailable (no file list) or file_list_indexing (retryable); 404 path_not_found; 422 invalid_path.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
path |
query | string | no | Absolute path (/, /home/user, no trailing slash). |
limit |
query | integer | no | |
cursor |
query | string | no | |
owner |
query | "platform" or "organization" | no | Pick the platform template even when an organization template shadows its slug. |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes | |
version |
path | integer | yes |
Responses: 200 TemplateFilePage · 4XX ErrorBody · 5XX ErrorBody
GET /v1/templates/{slug}/versions/{version}/files
List one directory of a template version’s file tree.
Entries directly inside path (default /), sorted by name bytes, keyset-paginated. The tree covers the whole filesystem (only the contents of /proc, /sys, /dev, /run and /tmp are left out). 409 file_list_unavailable (no file list) or file_list_indexing (retryable); 404 path_not_found; 422 invalid_path.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
path |
query | string | no | Absolute path (/, /home/user, no trailing slash). |
limit |
query | integer | no | |
cursor |
query | string | no | |
owner |
query | "platform" or "organization" | no | Pick the platform template even when an organization template shadows its slug. |
slug |
path | string | yes | |
version |
path | integer | yes |
Responses: 200 TemplateFilePage · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/templates/{slug}/versions/{version}/files/entry
Get one entry of a template version’s file tree.
404 path_not_found; 409 file_list_unavailable / file_list_indexing; 422 invalid_path.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
path |
query | string | yes | Absolute path (/, /home/user, no trailing slash). |
owner |
query | "platform" or "organization" | no | Pick the platform template even when an organization template shadows its slug. |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes | |
version |
path | integer | yes |
Responses: 200 TemplateFileEntry · 4XX ErrorBody · 5XX ErrorBody
GET /v1/templates/{slug}/versions/{version}/files/entry
Get one entry of a template version’s file tree.
404 path_not_found; 409 file_list_unavailable / file_list_indexing; 422 invalid_path.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
path |
query | string | yes | Absolute path (/, /home/user, no trailing slash). |
owner |
query | "platform" or "organization" | no | Pick the platform template even when an organization template shadows its slug. |
slug |
path | string | yes | |
version |
path | integer | yes |
Responses: 200 TemplateFileEntry · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/templates/{slug}/diff
Diff two versions of a template (path, change, before, after).
from is a version number of this template or base (the to version’s build base, which may be a platform version). Keyset-paginated by path; path_prefix narrows it (string prefix), change filters one kind. The first page (no cursor) carries summary. 409 file_list_unavailable / file_list_indexing when either version has no loaded file list.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
from |
query | string | yes | A version number, or base. |
to |
query | integer | yes | |
path_prefix |
query | string | no | |
change |
query | "added" or "removed" or "changed" or "type_changed" or "metadata" | no | |
limit |
query | integer | no | |
cursor |
query | string | no | |
owner |
query | "platform" or "organization" | no | Pick the platform template even when an organization template shadows its slug. |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes |
Responses: 200 TemplateDiffPage · 4XX ErrorBody · 5XX ErrorBody
GET /v1/templates/{slug}/diff
Diff two versions of a template (path, change, before, after).
from is a version number of this template or base (the to version’s build base, which may be a platform version). Keyset-paginated by path; path_prefix narrows it (string prefix), change filters one kind. The first page (no cursor) carries summary. 409 file_list_unavailable / file_list_indexing when either version has no loaded file list.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
from |
query | string | yes | A version number, or base. |
to |
query | integer | yes | |
path_prefix |
query | string | no | |
change |
query | "added" or "removed" or "changed" or "type_changed" or "metadata" | no | |
limit |
query | integer | no | |
cursor |
query | string | no | |
owner |
query | "platform" or "organization" | no | Pick the platform template even when an organization template shadows its slug. |
slug |
path | string | yes |
Responses: 200 TemplateDiffPage · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/templates/{slug}/versions/{version}/recipe
Export the recipe and settings of a template version (request form, ready to build again).
recipe: v1 {base, dockerfile, network} or the recipe v2 document (base as <slug>@<version>, languages as {id, version}, files without size, settings as stored); building it again from the same base with the same API release, while its uploads exist, gives the same recipe_sha256. null for versions saved from a workspace or published by the platform. settings: the version’s TemplateSettings. Same visibility as the version (unpublished only for owners/admins of the owning organization).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
owner |
query | "platform" or "organization" | no | Pick the platform template even when an organization template shadows its slug. |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes | |
version |
path | integer | yes |
Responses: 200 TemplateVersionRecipe · 4XX ErrorBody · 5XX ErrorBody
GET /v1/templates/{slug}/versions/{version}/recipe
Export the recipe and settings of a template version (request form, ready to build again).
recipe: v1 {base, dockerfile, network} or the recipe v2 document (base as <slug>@<version>, languages as {id, version}, files without size, settings as stored); building it again from the same base with the same API release, while its uploads exist, gives the same recipe_sha256. null for versions saved from a workspace or published by the platform. settings: the version’s TemplateSettings. Same visibility as the version (unpublished only for owners/admins of the owning organization).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
owner |
query | "platform" or "organization" | no | Pick the platform template even when an organization template shadows its slug. |
slug |
path | string | yes | |
version |
path | integer | yes |
Responses: 200 TemplateVersionRecipe · 4XX ErrorBody · 5XX ErrorBody
Template builds
Build template versions from a recipe or an uploaded context, and look up packages and languages.
GET /v1/organizations/{organization_id}/template-builds
List an organization’s template builds, newest first.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
state |
query | string (one of 8) | no | |
template |
query | string | no | Organization template slug. |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/organizations/{organization_id}/template-builds
Request a custom template build (queued for the isolated builder).
202 with the queued build. Takes recipe v1 (a Dockerfile) or recipe v2 (: validated against the base, compiled to the builder’s steps, uploads locked). Records the canonical recipe and its SHA-256 (provenance input), pins the base version, clamps resources to the plan and assigns target_version. The cell executes it (building -> testing -> publishing -> published) and the API then registers the version (published at once unless auto_publish=false); poll GET .../template-builds/{id} until registration.state is registered (or the build failed/was canceled). builder_availability says whether a builder is running. Errors: 422 when the recipe is outside the host builder Dockerfile dialect or the slug is not a builder slug (details.reason: multi_stage_not_supported, from_not_template_base, stage_names_not_supported, from_flags_not_supported, base_mismatch, run_flags_not_supported, heredoc_not_supported, instruction_not_supported, no_build_context, env_invalid, user_invalid, too_many_steps, recipe_too_large, slug_not_supported_by_builder, platform_template_slug, reserved_template_slug, base_not_found, base_not_published, base_archived, architecture_not_supported, ...; line for line errors; recipe v2: invalid_recipe (details.field, details.detail), base_not_layered, language_unavailable, language_conflict, invalid_package, too_many_files, platform_owned_path, invalid_path, upload_required, upload_missing, upload_digest_mismatch, upload_too_large, too_many_steps, recipe_too_large, allowlist_empty, allow_hosts_without_allowlist, extra_hosts_without_auto, too_many_hosts, invalid_host, ip_literal_not_allowed, host_not_allowed, invalid_settings, services_unsupported, too_many_acknowledged_findings; immutable paths: invalid_path (details.field recipe.immutable[<i>]), immutable_path_removed (the recipe drops a path of the template’s open version; details.removed), immutable_paths_unsupported_base (a non-empty list on a base whose guest agent lacks immutable_paths.v1, or a recipe v1 build of a template with immutable paths; details.base {name, version})), 503 dependency_unavailable (uploads_not_configured), 402 entitlement_required, 429 rate_limited (template_build_queue_full: the plan’s queued_template_builds (10 by default) already queued past its concurrent_template_builds; retryable, Retry-After; a build past the plan’s slots is accepted and waits, its queue gives the position and expected wait), 409 template_archived, 402 card_required (no card on file; details.add_card_url). Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
template_slug |
string | yes | Organization template to build into (created by the first build). Platform slugs and the reserved slugs new and edit are refused for new templates. |
auto_publish |
boolean | no | Publish the produced version as soon as it is registered (new workspaces of the slug then use it). false: it stays unpublished until an owner/admin publishes it. |
display_name |
string | no | Template name when this build creates the template. |
recipe |
TemplateRecipeV1 or TemplateRecipeV2 | yes | Recipe v1 (TemplateRecipeV1: a Dockerfile, no schema field) or recipe v2 (TemplateRecipeV2, schema: "shardflux.template-recipe.v2": languages, packages, uploaded files, build steps, auto network and settings;). |
description |
string | no | The version description (manifest description). |
acknowledged_scan_findings |
array of string | no | Recipe v2 only: up to 200 absolute paths the credential scan may report without failing the build (recorded in the manifest). |
Responses: 202 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/template-builds/{build_id}
Get a template build: state, timestamps, bounds, provenance, results, failure, log tail and builder availability.
Bounded wait: with Prefer: wait=<seconds> (at most 20) and a build that is not settled (settled = failed, canceled, legacy succeeded, or published with registration registered/failed), the response is held until state or registration.state changes or the wait elapses, then carries the fresh build and Preference-Applied: wait=<seconds>. Without Preference-Applied the server did not wait: poll with backoff.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
build_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
prefer |
header | string | no | RFC 7240 preference, e.g. wait=20. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/organizations/{organization_id}/template-builds/{build_id}/cancel
Cancel a template build.
queued -> canceled (200); building/testing/publishing -> cancellation requested (202; honoured until the artifact upload starts, so a build in publishing may still end published); canceled -> unchanged (200); published/succeeded/failed -> 409 build_finished. API keys may cancel only builds they requested.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
build_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/template-builds/{build_id}/log-url
Get a short-lived download URL for the full build log.
Presigned S3 GET of builds/<build_id>.log (served as an attachment build-<id>.log, text/plain), valid until expires_at (at most 15 minutes). Same access as reading the build. 404 not_found with details.reason log_not_available (no full log recorded for this build, e.g. still running) or log_expired (past its retention); 503 dependency_unavailable when this deployment has no build-log bucket. Audited; never cached.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
build_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/organizations/{organization_id}/template-uploads
Request an upload of a recipe build input (a file, or a folder as an uncompressed tar), by content.
200 {upload, put: null} when the organization already has these bytes (a pending upload is checked in the bucket first). 201 with a presigned S3 PUT otherwise (valid 900 s): send the bytes with every header of put.headers (x-amz-checksum-sha256 and content-length are signed, so S3 refuses other bytes), then reference sha256:<hex> in the recipe’s build.files. Idempotent by content (no Idempotency-Key needed). Errors: 422 upload_too_large (details.limit upload_bytes_max: at most 5 GiB), 422 upload_digest_mismatch (the same sha256 is available with another size), 503 dependency_unavailable (uploads_not_configured). Owners/admins and API keys with a tool permission.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required, TemplateUploadRequest)
| Field | Type | Required | Description |
|---|---|---|---|
sha256 |
string | yes | Lower-case hex SHA-256 of the bytes. |
size |
integer | yes | Bytes, at most 5368709120 (5 GiB; more is 422 upload_too_large). |
kind |
"file" or "tar" | yes | file, or tar (an uncompressed ustar/pax archive of a folder). Recorded for display; the recipe entry decides. |
Responses: 200 TemplateUploadResponse · 201 TemplateUploadResponse · 4XX ErrorBody · 5XX ErrorBody
POST /v1/template-uploads
Request an upload of a recipe build input (a file, or a folder as an uncompressed tar), by content (the API key’s organization).
200 {upload, put: null} when the organization already has these bytes (a pending upload is checked in the bucket first). 201 with a presigned S3 PUT otherwise (valid 900 s): send the bytes with every header of put.headers (x-amz-checksum-sha256 and content-length are signed, so S3 refuses other bytes), then reference sha256:<hex> in the recipe’s build.files. Idempotent by content (no Idempotency-Key needed). Errors: 422 upload_too_large (details.limit upload_bytes_max: at most 5 GiB), 422 upload_digest_mismatch (the same sha256 is available with another size), 503 dependency_unavailable (uploads_not_configured). Owners/admins and API keys with a tool permission.
Authentication: API key
Request body (application/json, required, TemplateUploadRequest)
| Field | Type | Required | Description |
|---|---|---|---|
sha256 |
string | yes | Lower-case hex SHA-256 of the bytes. |
size |
integer | yes | Bytes, at most 5368709120 (5 GiB; more is 422 upload_too_large). |
kind |
"file" or "tar" | yes | file, or tar (an uncompressed ustar/pax archive of a folder). Recorded for display; the recipe entry decides. |
Responses: 200 TemplateUploadResponse · 201 TemplateUploadResponse · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/template-packages
Search apt, pip or npm packages for a recipe.
apt: the base’s package index (409 package_index_unavailable when the base has none); pip: the daily PyPI name list (names only; 409 package_index_unavailable before its first refresh); npm: the registry search. Upstreams are bounded (503 dependency_unavailable, retryable, when they do not answer within 2.5 s) and each organization may make 120 lookups a minute (429 rate_limited). Owners/admins and API keys with a tool permission.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
ecosystem |
query | "apt" or "pip" or "npm" | yes | |
q |
query | string | yes | |
base |
query | string | no | <slug>@<version> (apt: the base whose apt index is searched). |
limit |
query | integer | no | |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 TemplatePackagePage · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/template-packages/{ecosystem}/{name}
Get one apt, pip or npm package: latest version, summary and versions.
404 not_found (package_not_found). apt needs base=<slug>@<version>. Same limits as the search.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
base |
query | string | no | <slug>@<version> (apt: the base whose apt index is searched). |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
ecosystem |
path | "apt" or "pip" or "npm" | yes | |
name |
path | string | yes |
Responses: 200 TemplatePackage · 4XX ErrorBody · 5XX ErrorBody
GET /v1/template-packages
Search apt, pip or npm packages for a recipe (the API key’s organization).
apt: the base’s package index (409 package_index_unavailable when the base has none); pip: the daily PyPI name list (names only; 409 package_index_unavailable before its first refresh); npm: the registry search. Upstreams are bounded (503 dependency_unavailable, retryable, when they do not answer within 2.5 s) and each organization may make 120 lookups a minute (429 rate_limited). Owners/admins and API keys with a tool permission.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
ecosystem |
query | "apt" or "pip" or "npm" | yes | |
q |
query | string | yes | |
base |
query | string | no | <slug>@<version> (apt: the base whose apt index is searched). |
limit |
query | integer | no |
Responses: 200 TemplatePackagePage · 4XX ErrorBody · 5XX ErrorBody
GET /v1/template-packages/{ecosystem}/{name}
Get one apt, pip or npm package: latest version, summary and versions (the API key’s organization).
404 not_found (package_not_found). apt needs base=<slug>@<version>. Same limits as the search.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
base |
query | string | no | <slug>@<version> (apt: the base whose apt index is searched). |
ecosystem |
path | "apt" or "pip" or "npm" | yes | |
name |
path | string | yes |
Responses: 200 TemplatePackage · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/template-languages
The recipe languages a base offers: version, default, whether the base already has it, and the hosts its install needs.
The language table read for the chain’s platform base of base, resolved as a build resolves recipe.base (422 validation_failed with details.field "base" and the build’s reasons: base_not_found, base_archived, base_not_published, architecture_not_supported). included: the base already has that version (python-node-browser: python and node), so the build installs nothing for it. A version the base has another version of is left out (a build would refuse it with language_conflict). Owners/admins and API keys with a tool permission.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
base |
query | string | yes | <slug>@<version> (apt: the base whose apt index is searched). |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 TemplateLanguages · 4XX ErrorBody · 5XX ErrorBody
GET /v1/template-languages
The recipe languages a base offers: version, default, whether the base already has it, and the hosts its install needs (the API key’s organization).
The language table read for the chain’s platform base of base, resolved as a build resolves recipe.base (422 validation_failed with details.field "base" and the build’s reasons: base_not_found, base_archived, base_not_published, architecture_not_supported). included: the base already has that version (python-node-browser: python and node), so the build installs nothing for it. A version the base has another version of is left out (a build would refuse it with language_conflict). Owners/admins and API keys with a tool permission.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
base |
query | string | yes | <slug>@<version> (apt: the base whose apt index is searched). |
Responses: 200 TemplateLanguages · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/template-builder-availability
Whether a template builder is running (heartbeat within 60 s).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
Template drafts
Draft templates: iterate on states and test instances, then publish a version.
GET /v1/organizations/{organization_id}/templates/{slug}/draft
Get the template’s draft.
404 draft_not_found when the template has no live draft.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes |
Responses: 200 TemplateDraft · 4XX ErrorBody · 5XX ErrorBody
POST /v1/organizations/{organization_id}/templates/{slug}/draft
Create the template’s draft (a layered workspace on the draft base).
Opens the organization template’s single live draft (key sf:draft:<slug>:<8 hex>, purpose template_draft, persistent, layered) on base (default: the latest published version; required when the template has none, which creates the organization template named display_name, default the slug). 202 with the open operation and the draft. Errors: 409 template_not_layered (the base is not layered-capable, or layered opens are off), 409 draft_exists (details.workspace_id), 403 template_dev_mode_role, 422 platform_template_slug / reserved_template_slug (new, edit) / base_required; 402/403/429 as for open (admitted like a start: a concurrent_workspaces refusal is queued up to 10 s, then 429 quota_exceeded with Retry-After). tool_token is null when the tool-token rate limit is used up. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes |
Request body (application/json, required, CreateDraftBody)
| Field | Type | Required | Description |
|---|---|---|---|
base |
string | no | <slug>@<version>: a published, layered-capable version (default: the template’s latest published version; required when the template has none, which creates the organization template). |
project_id |
string (uuid) | no | UUIDv7, lowercase canonical form. |
display_name |
string | no | Template name when this draft creates the organization template (default: the slug). |
caps |
object | no | |
agent_label |
string | no | |
tools |
array of string (one of 7) | no | |
inputs |
object | no | Open-time inputs of the template version: {NAME: string} for its declared text inputs. A new workspace stores each given value, else the declared default; on an existing key inputs replaces them all (omitted = unchanged). Secret inputs are not passed here: they bind the stored secret of the same name. 422 input_unknown (undeclared name, details.names), input_invalid (a secret input, a non-string, or a value over 4096 bytes or with CR, LF or NUL; details.names), input_required (details {names, kind}). |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
DELETE /v1/organizations/{organization_id}/templates/{slug}/draft
Discard the draft (delete it and end its live test instances).
Deletes the draft (operation delete, input.reason draft_discarded) and ends each live test instance (ended_reason draft_discarded). 202 with the draft’s delete operation. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes |
Responses: 202 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/templates/{slug}/draft
Get the template’s draft.
404 draft_not_found when the template has no live draft.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
slug |
path | string | yes |
Responses: 200 TemplateDraft · 4XX ErrorBody · 5XX ErrorBody
POST /v1/templates/{slug}/draft
Create the template’s draft (a layered workspace on the draft base).
Opens the organization template’s single live draft (key sf:draft:<slug>:<8 hex>, purpose template_draft, persistent, layered) on base (default: the latest published version; required when the template has none, which creates the organization template named display_name, default the slug). 202 with the open operation and the draft. Errors: 409 template_not_layered (the base is not layered-capable, or layered opens are off), 409 draft_exists (details.workspace_id), 403 template_dev_mode_role, 422 platform_template_slug / reserved_template_slug (new, edit) / base_required; 402/403/429 as for open (admitted like a start: a concurrent_workspaces refusal is queued up to 10 s, then 429 quota_exceeded with Retry-After). tool_token is null when the tool-token rate limit is used up. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
slug |
path | string | yes |
Request body (application/json, required, CreateDraftBody)
| Field | Type | Required | Description |
|---|---|---|---|
base |
string | no | <slug>@<version>: a published, layered-capable version (default: the template’s latest published version; required when the template has none, which creates the organization template). |
project_id |
string (uuid) | no | UUIDv7, lowercase canonical form. |
display_name |
string | no | Template name when this draft creates the organization template (default: the slug). |
caps |
object | no | |
agent_label |
string | no | |
tools |
array of string (one of 7) | no | |
inputs |
object | no | Open-time inputs of the template version: {NAME: string} for its declared text inputs. A new workspace stores each given value, else the declared default; on an existing key inputs replaces them all (omitted = unchanged). Secret inputs are not passed here: they bind the stored secret of the same name. 422 input_unknown (undeclared name, details.names), input_invalid (a secret input, a non-string, or a value over 4096 bytes or with CR, LF or NUL; details.names), input_required (details {names, kind}). |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
DELETE /v1/templates/{slug}/draft
Discard the draft (delete it and end its live test instances).
Deletes the draft (operation delete, input.reason draft_discarded) and ends each live test instance (ended_reason draft_discarded). 202 with the draft’s delete operation. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
slug |
path | string | yes |
Responses: 202 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/templates/{slug}/draft/states
List the draft’s states, newest first.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/organizations/{organization_id}/templates/{slug}/draft/states
Capture a draft state (disk-only layer_snapshot of the running draft).
202 with the layer_snapshot operation; its result carries checkpoint_id. A suspended draft is 409 workspace_not_running (its current checkpoint already serves as a state). Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
label |
string | no |
Responses: 202 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/templates/{slug}/draft/states
List the draft’s states, newest first.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
slug |
path | string | yes |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/templates/{slug}/draft/states
Capture a draft state (disk-only layer_snapshot of the running draft).
202 with the layer_snapshot operation; its result carries checkpoint_id. A suspended draft is 409 workspace_not_running (its current checkpoint already serves as a state). Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
slug |
path | string | yes |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
label |
string | no |
Responses: 202 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/templates/{slug}/draft/test-instances
List the draft’s test instances.
Live instances; include_ended=true adds ended ones (tombstones with ended_reason). API keys see only their own project’s instances.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
include_ended |
query | boolean | no | |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/organizations/{organization_id}/templates/{slug}/draft/test-instances
Open a test instance of a draft state (a disposable session workspace).
Opens a layered session workspace (purpose template_test) on the draft base whose layer is a copy of the draft state; its own writes never reach the draft. Without state_id the running draft is captured first (the instance’s open depends on that layer_snapshot; origin.checkpoint_id is filled when the cell places it); a suspended draft’s current checkpoint is used instead. The instance ends on close(), idle or draft discard. 202 OpenResponse. 402/403/429 as for open (admitted like a start: a concurrent_workspaces refusal is queued up to 10 s, then 429 quota_exceeded with Retry-After). Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes |
Request body (application/json, required, CreateTestInstanceBody)
| Field | Type | Required | Description |
|---|---|---|---|
state_id |
string (uuid) | no | A draft state (GET …/draft/states). Omitted: a fresh capture of the running draft (its current checkpoint when suspended). |
key |
string | no | Workspace key (default sf:test:<slug>:<8 hex>); keys starting with sf: are reserved. |
caps |
object | no | |
agent_label |
string | no | |
tools |
array of string (one of 7) | no | |
inputs |
object | no | Open-time inputs of the template version: {NAME: string} for its declared text inputs. A new workspace stores each given value, else the declared default; on an existing key inputs replaces them all (omitted = unchanged). Secret inputs are not passed here: they bind the stored secret of the same name. 422 input_unknown (undeclared name, details.names), input_invalid (a secret input, a non-string, or a value over 4096 bytes or with CR, LF or NUL; details.names), input_required (details {names, kind}). |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/templates/{slug}/draft/test-instances
List the draft’s test instances.
Live instances; include_ended=true adds ended ones (tombstones with ended_reason). API keys see only their own project’s instances.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
include_ended |
query | boolean | no | |
slug |
path | string | yes |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/templates/{slug}/draft/test-instances
Open a test instance of a draft state (a disposable session workspace).
Opens a layered session workspace (purpose template_test) on the draft base whose layer is a copy of the draft state; its own writes never reach the draft. Without state_id the running draft is captured first (the instance’s open depends on that layer_snapshot; origin.checkpoint_id is filled when the cell places it); a suspended draft’s current checkpoint is used instead. The instance ends on close(), idle or draft discard. 202 OpenResponse. 402/403/429 as for open (admitted like a start: a concurrent_workspaces refusal is queued up to 10 s, then 429 quota_exceeded with Retry-After). Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
slug |
path | string | yes |
Request body (application/json, required, CreateTestInstanceBody)
| Field | Type | Required | Description |
|---|---|---|---|
state_id |
string (uuid) | no | A draft state (GET …/draft/states). Omitted: a fresh capture of the running draft (its current checkpoint when suspended). |
key |
string | no | Workspace key (default sf:test:<slug>:<8 hex>); keys starting with sf: are reserved. |
caps |
object | no | |
agent_label |
string | no | |
tools |
array of string (one of 7) | no | |
inputs |
object | no | Open-time inputs of the template version: {NAME: string} for its declared text inputs. A new workspace stores each given value, else the declared default; on an existing key inputs replaces them all (omitted = unchanged). Secret inputs are not passed here: they bind the stored secret of the same name. 422 input_unknown (undeclared name, details.names), input_invalid (a secret input, a non-string, or a value over 4096 bytes or with CR, LF or NUL; details.names), input_required (details {names, kind}). |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/organizations/{organization_id}/templates/{slug}/draft/publish
Publish the draft as the template’s next version (save-as-template from the draft).
Builds one new org layer holding every change since the draft base, from state_id or the draft’s current state (a fresh capture when running). settings (TemplateSettingsInput): each given field replaces that field of the draft base’s settings, each absent one is carried forward (settings.defaults and defaults together: 422 invalid_settings). Refused with 409 draft_stale (details latest_version, draft_base_version) when the template has a version newer than the draft base that this draft did not produce, and 409 build_in_progress while a build from another source is unfinished. Immutable paths: the new version declares the template’s open version’s immutable paths (else the draft base’s) with the content the draft holds there; 422 immutable_paths_unsupported_base when the draft’s base has no guest agent with immutable_paths.v1 (details.base). 202 SaveAsTemplateResponse. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes |
Request body (application/json, required, PublishDraftBody)
| Field | Type | Required | Description |
|---|---|---|---|
state_id |
string (uuid) | no | UUIDv7, lowercase canonical form. |
description |
string | no | |
defaults |
object | no | Defaults of the new version (omitted fields: persistent lifetime, platform idle timeout, no limits). |
settings |
TemplateSettingsInput | no | What a workspace of the version gets when it opens (). Omitted fields are empty (recipes do not carry settings forward). |
auto_publish |
boolean | no | |
acknowledged_scan_findings |
array of string | no | Up to 200 absolute paths the credential scan may report without failing the build (recorded in the manifest). |
Responses: 202 SaveAsTemplateResponse · 4XX ErrorBody · 5XX ErrorBody
POST /v1/templates/{slug}/draft/publish
Publish the draft as the template’s next version (save-as-template from the draft).
Builds one new org layer holding every change since the draft base, from state_id or the draft’s current state (a fresh capture when running). settings (TemplateSettingsInput): each given field replaces that field of the draft base’s settings, each absent one is carried forward (settings.defaults and defaults together: 422 invalid_settings). Refused with 409 draft_stale (details latest_version, draft_base_version) when the template has a version newer than the draft base that this draft did not produce, and 409 build_in_progress while a build from another source is unfinished. Immutable paths: the new version declares the template’s open version’s immutable paths (else the draft base’s) with the content the draft holds there; 422 immutable_paths_unsupported_base when the draft’s base has no guest agent with immutable_paths.v1 (details.base). 202 SaveAsTemplateResponse. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
slug |
path | string | yes |
Request body (application/json, required, PublishDraftBody)
| Field | Type | Required | Description |
|---|---|---|---|
state_id |
string (uuid) | no | UUIDv7, lowercase canonical form. |
description |
string | no | |
defaults |
object | no | Defaults of the new version (omitted fields: persistent lifetime, platform idle timeout, no limits). |
settings |
TemplateSettingsInput | no | What a workspace of the version gets when it opens (). Omitted fields are empty (recipes do not carry settings forward). |
auto_publish |
boolean | no | |
acknowledged_scan_findings |
array of string | no | Up to 200 absolute paths the credential scan may report without failing the build (recorded in the manifest). |
Responses: 202 SaveAsTemplateResponse · 4XX ErrorBody · 5XX ErrorBody
POST /v1/organizations/{organization_id}/templates/{slug}/versions/{version}/test-instances
Open a test instance of a template version, published or not.
Opens a layered session workspace (purpose template_test) on that version of the organization’s template: registered and not archived, published or not. It is a fresh workspace (a create startup run) with origin {kind: template_version, template_id, version}; it ends on close() or idle. inputs as for open (422 input_unknown, input_invalid, input_required). 202 OpenResponse (200 when an existing key is running and ready). Errors: 403 template_dev_mode_role (owners, admins and API keys with a tool permission only), 404 version_not_found, 409 template_not_layered, template_archived, 422 reserved_key_prefix; 402/403/429 as for open (admitted like a start: a concurrent_workspaces refusal is queued up to 10 s, then 429 quota_exceeded with Retry-After). tool_token is null when the tool-token rate limit is used up. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
slug |
path | string | yes | |
version |
path | integer | yes |
Request body (application/json, required, CreateVersionTestInstanceBody)
| Field | Type | Required | Description |
|---|---|---|---|
key |
string | no | Workspace key (default sf:test:<slug>:<8 hex>); caller keys starting with sf: are reserved. |
caps |
object | no | |
agent_label |
string | no | |
tools |
array of string (one of 7) | no | |
inputs |
object | no | Open-time inputs of the template version: {NAME: string} for its declared text inputs. A new workspace stores each given value, else the declared default; on an existing key inputs replaces them all (omitted = unchanged). Secret inputs are not passed here: they bind the stored secret of the same name. 422 input_unknown (undeclared name, details.names), input_invalid (a secret input, a non-string, or a value over 4096 bytes or with CR, LF or NUL; details.names), input_required (details {names, kind}). |
project_id |
string (uuid) | no | UUIDv7, lowercase canonical form. |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/templates/{slug}/versions/{version}/test-instances
Open a test instance of a template version, published or not.
Opens a layered session workspace (purpose template_test) on that version of the organization’s template: registered and not archived, published or not. It is a fresh workspace (a create startup run) with origin {kind: template_version, template_id, version}; it ends on close() or idle. inputs as for open (422 input_unknown, input_invalid, input_required). 202 OpenResponse (200 when an existing key is running and ready). Errors: 403 template_dev_mode_role (owners, admins and API keys with a tool permission only), 404 version_not_found, 409 template_not_layered, template_archived, 422 reserved_key_prefix; 402/403/429 as for open (admitted like a start: a concurrent_workspaces refusal is queued up to 10 s, then 429 quota_exceeded with Retry-After). tool_token is null when the tool-token rate limit is used up. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
slug |
path | string | yes | |
version |
path | integer | yes |
Request body (application/json, required, CreateVersionTestInstanceBody)
| Field | Type | Required | Description |
|---|---|---|---|
key |
string | no | Workspace key (default sf:test:<slug>:<8 hex>); caller keys starting with sf: are reserved. |
caps |
object | no | |
agent_label |
string | no | |
tools |
array of string (one of 7) | no | |
inputs |
object | no | Open-time inputs of the template version: {NAME: string} for its declared text inputs. A new workspace stores each given value, else the declared default; on an existing key inputs replaces them all (omitted = unchanged). Secret inputs are not passed here: they bind the stored secret of the same name. 422 input_unknown (undeclared name, details.names), input_invalid (a secret input, a non-string, or a value over 4096 bytes or with CR, LF or NUL; details.names), input_required (details {names, kind}). |
project_id |
string (uuid) | no | UUIDv7, lowercase canonical form. |
Responses: 200 · 202 · 4XX ErrorBody · 5XX ErrorBody
Secrets
Project secrets, their versions, and the secrets bound to a workspace.
GET /v1/projects/{project_id}/secrets
List a project’s secrets (metadata only).
Project-scoped secrets only; organization secrets are listed under the organization.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
include_deleted |
query | boolean | no | |
project_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/projects/{project_id}/secrets
Create a project secret (owner/admin, or an API key of this project).
The value is encrypted (KMS envelope) and never returned. allowed_tools defaults to [exec, pty]. 409 when the name exists in this project. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Environment-variable-safe name, unique per scope (SHARDFLUX_ prefix reserved). |
description |
string | no | |
value |
string | yes | UTF-8 text, at most 65536 bytes, no NUL characters. Write-only: never returned by any management API. |
allowed_workspace_ids |
array of string (uuid) or null | no | Default null (any workspace of the project). |
allowed_tools |
array of "exec" or "files" or "pty" or "process" or "git" or "browser" | no |
Responses: 201 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/secrets/{secret_id}
Get secret metadata (never the value).
Deleted secrets stay readable (deleted_at set).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
secret_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
PATCH /v1/secrets/{secret_id}
Update a secret’s description or usage permissions.
Takes effect for the next session start that resolves it. Names are immutable (delete and recreate).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
secret_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
description |
string | no | |
allowed_project_ids |
array of string (uuid) or null | no | |
allowed_workspace_ids |
array of string (uuid) or null | no | |
allowed_tools |
array of "exec" or "files" or "pty" or "process" or "git" or "browser" | no |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
DELETE /v1/secrets/{secret_id}
Delete a secret (tombstone; erases every stored value; resolution stops immediately).
Any session start after this returns it as denied. The name becomes reusable. The name is removed from every workspace binding that referred to this secret (same transaction, one audit event per workspace). Values already injected into running processes cannot be recalled. Repeating returns 204.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
secret_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 204 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/secrets/{secret_id}/versions
List a secret’s versions, newest first (metadata only).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
secret_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
POST /v1/secrets/{secret_id}/versions
Rotate: store a new value as the next version.
The new version is used from the next session start; every older version’s value is erased in the same transaction (metadata kept). Values already injected into running processes are not recalled. Supports Idempotency-Key.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
secret_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
value |
string | yes | UTF-8 text, at most 65536 bytes, no NUL characters. Write-only: never returned by any management API. |
Responses: 201 · 4XX ErrorBody · 5XX ErrorBody
Egress policy
Outbound network policy of a project or a workspace.
GET /v1/projects/{project_id}/egress-policy
Get a project’s egress policy (default for its workspaces).
version (also the ETag) is the value to send as If-Match. Without a project policy the effective policy is the platform default (allow_all).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
PUT /v1/projects/{project_id}/egress-policy
Replace a project’s egress policy (creates a new immutable version).
mode allow_all | allowlist | deny_all; rules/cidrs only with allowlist. Hosts: exact FQDN or *. single-label wildcard (IDNA-normalized, lower-case); IP literals and localhost-like names are rejected. Ports 1-65535 (default [443]); protocols tcp only. cidrs: public ranges only. Optional If-Match: <version> (409 conflict with details.reason version_mismatch). Owner/admin or an API key of this project with tool permissions.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
if-match |
header | string | no |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
mode |
"allow_all" or "allowlist" or "deny_all" | yes | |
rules |
array of object | no | allowlist only; at most 256. |
cidrs |
array of string | no | allowlist only; public ranges; at most 64. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/projects/{project_id}/egress-policy/versions
Version history of a project’s egress policy, newest first.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
project_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/workspaces/{workspace_id}/egress-policy
Effective egress policy of a workspace and its enforcement state.
effective = workspace override > project policy > platform default (allow_all). enforcement.state is not_enforced until the cell/host acknowledges the current effective policy for the workspace’s current ownership epoch, then pending | enforced | failed. version (ETag) is the override history version for If-Match. template_egress: the template version’s egress ceiling (, null = none); effective_policy: what the host enforces, effective intersected with that ceiling.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
PUT /v1/workspaces/{workspace_id}/egress-policy
Set a workspace override of the egress policy (new immutable version).
Same body and validation as the project policy. Optional If-Match. 409 conflict (details.reason workspace_deleted) for deleted workspaces. 422 egress_widening (details {template_egress, outside: [hosts] | ["cidrs"] | ["allow_all"]}) when the workspace’s template egress ceiling would narrow the policy.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
if-match |
header | string | no |
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
mode |
"allow_all" or "allowlist" or "deny_all" | yes | |
rules |
array of object | no | allowlist only; at most 256. |
cidrs |
array of string | no | allowlist only; public ranges; at most 64. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
DELETE /v1/workspaces/{workspace_id}/egress-policy
Remove the workspace override (fall back to the project policy).
Records a cleared version. 204 without a new version when there is no active override. Optional If-Match.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
if-match |
header | string | no |
Responses: 204 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/workspaces/{workspace_id}/egress-policy/versions
Version history of a workspace’s egress override (including cleared versions), newest first.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
Usage and spend
Metered usage, estimates, grants and spend of an organization or a workspace.
GET /v1/usage/summary
Current-period project usage.
The project and organization come from the guest delegation or project key. User sessions supply project_id. Meter quantities and measurement are restricted to that project.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
query | string (uuid) | no | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/usage/summary
Current-period usage, included allowances and cap state of an organization.
Totals come from the usage ledger (hourly, finalized after the lateness window); measurement.measured_through says how far usage is complete. Billable units are whole units (floor per workspace-hour); allowance usage counts billable units. allowance_exhausted true means new opens/resumes answer 402 allowance_exhausted with exhausted_reason as details.reason. spend_cap is opt-in overage this period: while it is on, a CPU-hours or RAM GiB-hours allowance past included is in cap state overage (starts admitted, usage past it charged) until the spend cap is reached.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/usage
Usage time series (hour or day buckets) from the ledger.
Defaults to the current period at day granularity; hour granularity covers at most 31 days, day at most 400. workspace_id / volume_id (mutually exclusive) narrow it to one workspace or one shared volume (meter volume_storage_gib_seconds). API keys see only their own project.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
from |
query | string (date-time) | no | RFC 3339 UTC timestamp with Z. |
to |
query | string (date-time) | no | RFC 3339 UTC timestamp with Z. |
granularity |
query | "hour" or "day" | no | |
meter |
query | string (one of 7) | no | |
workspace_id |
query | string (uuid) | no | UUIDv7, lowercase canonical form. |
volume_id |
query | string (uuid) | no | UUIDv7, lowercase canonical form. |
project_id |
query | string (uuid) | no | UUIDv7, lowercase canonical form. |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/workspaces/{workspace_id}/usage
Usage of one workspace (time series and totals).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
from |
query | string (date-time) | no | RFC 3339 UTC timestamp with Z. |
to |
query | string (date-time) | no | RFC 3339 UTC timestamp with Z. |
granularity |
query | "hour" or "day" | no | |
meter |
query | string (one of 7) | no | |
workspace_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/usage/estimate
Period cost estimate: subscription fee, usage charges and projected allowance use.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/grants
Quotas, per-workspace ceilings/reservations/grants and compute budget leases.
Ceiling = min(template, user cap, plan cap). Reservations are organization-level (pending starts count). Leases are the compute budgets granted to the cell (<= 15 min). API keys see only their own project’s workspaces.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
cursor |
query | string | no | |
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/spend
Spend policy, charges this period, cap state and enforcement (leases, overshoot bound).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/spend-policy
Usage alert thresholds and opt-in overage with its spend cap.
Alert thresholds, and whether overage is available, on, off or paused, with the spend cap, its bounds and the rates. Overage is off by default; owners and billing members change it with the PUT (the console on /api/v1, or a CLI session on /v1; project API keys are refused).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
Entitlements
The limits and features the organization’s plan grants.
GET /v1/organizations/{organization_id}/entitlements
Resolved plan limits, allowances and policies of an organization.
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
Billing
The plan catalog and the organization’s subscription.
GET /v1/billing/catalog
The active plan catalog (the same versioned catalog admission enforces).
Public, cacheable. Prices are in minor units; purchasable plans can be bought with POST .../organizations/{id}/billing/checkout-sessions (a browser or CLI session; shard billing upgrade <plan>).
Authentication: None (public)
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
GET /v1/organizations/{organization_id}/billing/subscription
Subscription, billing state and effective plan of an organization.
Owner/billing members (browser) or any API key of the organization (read-only). billing_state: free (no paid subscription), active, canceling (ends at period end, after which a paid plan is required to open or resume compute), grace (payment overdue, workspaces unaffected), restricted (grace ended: new opens/resumes answer 402 entitlement_required).
Authentication: API key
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization_id |
path | string (uuid) | yes | UUIDv7, lowercase canonical form. |
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
Client versions
The latest and the oldest supported version of every Shardflux client package.
GET /v1/client-versions
Latest and minimum supported version of every Shardflux client.
Unauthenticated, Cache-Control: public, max-age=3600. Clients compare their own version: below minimum_supported they are unsupported, below the latest release outdated. Current clients take the latest release from the package registry (npm latest, PyPI) and use latest here as the fallback; hold names a release they stop recommending. They check at most every 12 hours (the CLI) or once per process (the SDKs), in the background, and never fail because of it.
Authentication: None (public)
Responses: 200 · 4XX ErrorBody · 5XX ErrorBody
Feedback
Send product feedback straight to the Shardflux team: what failed, confused you or is missing.
POST /v1/feedback
Send product feedback to the Shardflux team.
Delivered by email to the Shardflux team. Any valid project API key may send (no tool permission needed); the key's organization and project are recorded, so organization_id is rejected (422). A CLI session (sfu_) may send too, with an optional organization_id of the user's. Coding agents: send only when the user asks to report feedback, show the exact message and context first, obtain explicit authorization, and include context (agent, client, workspace, request_id and error_code of the error response, command). Rate limited per sender (10 per 10 minutes, 50 per day) and per organization (200 per day): 429 rate_limited with a retry-after header and details.retry_after_seconds. The same message from the same sender within 24 hours is a duplicate: 200 with the original id and duplicate: true (it still counts against the rate limits). The message and context take any length; a long one reaches the team whole, in several emails.
Authentication: API key
Request body (application/json, required)
| Field | Type | Required | Description |
|---|---|---|---|
message |
string | yes | The feedback, at any length (it must contain a non-whitespace character; surrounding whitespace is trimmed). Control characters other than newline and tab are stripped; API keys, bearer tokens, JWTs, AWS access key ids and private key blocks are redacted before it is stored or emailed. |
category |
"bug" or "confusing" or "missing" or "idea" or "praise" or "other" | no | Default other. bug: something failed or behaved wrongly; confusing: an error, doc, name or output was unclear or misleading; missing: a capability, option or template you needed does not exist; idea: a suggestion or improvement; praise: something worked well; other: anything else. |
organization_id |
string (uuid) | no | Sessions (browser, or a CLI session on /v1): the organization the user is looking at, one they belong to (404 otherwise). Refused (422) with a project API key, whose organization is used. |
context |
object | no | Optional, every field optional. Control characters are stripped and anything shaped like a secret is redacted before storing. |
Responses: 200 · 201 · 4XX ErrorBody · 5XX ErrorBody