Skip to content
Select themeSelect language

PATCH /workspaces/{id}/settings — update workspace settings (admin only). Carries the ADR 0045 onboarding-safe flag (graduating a workspace flips `onboarding_mode` to `false` and unlocks autonomous dispatch) and the backlog QUIET-HOURS window (Stage 1). All fields optional; see [`PatchWorkspaceSettingsRequest`]. Requires workspace admin (`ManageMembers`).

PATCH
/api/workspaces/{id}/settings
curl --request PATCH \
--url https://example.com/api/workspaces/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/settings \
--header 'Content-Type: application/json' \
--cookie supacloud_session=<supacloud_session> \
--data '{ "allow_debug": true, "autonomy_level": 1, "autonomy_overrides": "example", "calibration_carrier_preference": "subscription_first", "closeout_config": "example", "default_max_subagents": 1, "default_resource_class": "normal", "default_tool_approval_deny_minutes": 1, "digest_cadence": "example", "digest_enabled": true, "environment_scope_floor": "example", "failure_notify": { "chat_id": "example", "resource_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0" }, "harness_config": "example", "mcp_external_ops_hourly_limit": 1, "mcp_external_read_hourly_limit": 1, "memory_embedding": { "model": "example", "provider": "example" }, "memory_governance": { "personal": { "auto_publish": true, "require_review": true, "retention_days": 1 }, "project": { "auto_publish": true, "require_review": true, "retention_days": 1 }, "workspace": { "auto_publish": true, "require_review": true, "retention_days": 1 } }, "model_fallback_chains": "example", "onboarding_mode": true, "quiet_hours_end": "example", "quiet_hours_start": "example", "required_worker_group_ids": [ "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0" ], "system_llm_credential": { "id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "kind": "example" }, "vision_model": "example", "voice_config": "example" }'
id
required
string format: uuid

Workspace ID whose settings to patch

Media typeapplication/json

PATCH body for workspace settings (ADR 0045 Stage 1). All fields optional: onboarding_mode graduates/re-arms the onboarding guard; the quiet-hours pair configures (both set), clears (both null) or leaves unchanged (absent) the backlog dispatch QUIET-HOURS window. Times are HH:MM, interpreted UTC.

object
allow_debug

#583 Item 2 / #582 — the Debug-light CEILING (allow_debug). Absent = no change; a value SETS the ceiling. Workspace-admin only (the handler already gates PATCH on WorkspaceAction::ManageMembers). true lets members opt their own live/follow session into debug; false (re-)closes the ceiling.

boolean | null
autonomy_level

ADR 0045 Stage 4 — the workspace base autonomy level (0..=100). Absent = no change; null = clear back to the DEFAULT level; a value SETS the level (clamped to the plan ceiling before persist). DOUBLE-Option so the slider patch is distinguishable from an unrelated (onboarding/quiet-hours) patch.

integer | null format: int32
autonomy_overrides

ADR 0045 Stage 4 — the Advanced per-axis override vector. Absent leaves the stored overrides unchanged; a value replaces them ({} clears them). Only applied when the autonomy fields are touched.

calibration_carrier_preference
One of:
null
closeout_config

#712 — the workspace-tier standard-closeout config ({memory, project_docs, user_docs, wiki}, mig 283). Absent leaves the stored default unchanged; a value REPLACES the workspace default (shape-validated — a malformed bag is a 400). It is the DEFAULT every project inherits per step. Workspace-admin only (the handler gates PATCH on WorkspaceAction::ManageMembers).

default_max_subagents

#838 (mig 358) — the workspace-DEFAULT subagent concurrency ceiling. DOUBLE-Option: absent = no change; null = clear the default (the harness’s own behaviour applies unless a profile or the launch sets one); a value SETS it (1..=64, else 400). Workspace-admin only.

The upper bound tracks the container’s single memory allowance, which every subagent of a task shares — see workspaces::subagent_settings.

integer | null format: int32
default_resource_class
One of:
null
default_tool_approval_deny_minutes

#1175 (mig 363) — the workspace-DEFAULT tool-gate deny budget in minutes. DOUBLE-Option: absent = no change; null = clear the default (the built-in day applies unless a profile or the task names one); a value SETS it ([5, 43200], else 400). Workspace-admin only.

It governs the deny timeout policy ONLY. hold — the default for every runner-raised gate since #1164 — carries no deadline, and nothing set here can give it one.

Out of range is REFUSED, not clamped: a stored value that is not the value that applies is the failure this whole tier exists to avoid.

integer | null format: int32
digest_cadence

#519 #11 — the digest cadence (off | daily | weekly). Absent leaves it unchanged. An invalid value is a 400.

string | null
digest_enabled

#519 #11 — whether the autonomous-delivery notification digest is delivered. Absent leaves it unchanged. Applied together with digest_cadence (the pair is persisted whenever EITHER is present).

boolean | null
environment_scope_floor

#708 W8 — the workspace environment-scope FLOOR (all | production | staging | development, mig 290). Absent = no change; null = clear (no floor); a value MANDATES that no autonomous run acts outside that environment class (the blast radius is narrowed to it). An invalid value is a 400. Workspace-admin only.

string | null
failure_notify
One of:
null
harness_config

#1397 (mig 404) — the per-harness agent config bag ({"opencode": {...}}). DOUBLE-Option: absent = no change to the stored bag; null = clear it (no per-harness overrides); a value REPLACES it. The service validates the shape and REFUSES model/provider/mcp/enabled_providers with a 400 — those re-route an admitted run and are not guardrails. Workspace-admin only.

mcp_external_ops_hourly_limit

#977 — the external-MCP OPS per-token hourly aggregate override (mig 330). DOUBLE-Option: absent = no change; null = clear back to the env/default (30/h); a value SETS it (1..=100000, else 400). Workspace-admin only.

integer | null format: int32
mcp_external_read_hourly_limit

#977 — the external-MCP READ per-tool hourly-window override (mig 330). DOUBLE-Option: absent = no change; null = clear back to the built-in window; a value SETS it (1..=100000, else 400). Workspace-admin only.

integer | null format: int32
memory_embedding
One of:
null
memory_governance
One of:
null
model_fallback_chains

#926 — the workspace-level quota-fallback chain OVERRIDE (#867 substrate, workspace_settings.model_fallback_chains): a sparse per-tier JSON object ({"flagship": [{"agent":"claude","model":"…"}, …], …}) consumed by TierChains::from_json. DOUBLE-Option: absent = no change; null = clear back to the built-in chains; a value REPLACES the override (validated fail-closed — an unknown tier key or malformed entry is a 400). Workspace-admin only.

onboarding_mode

Onboarding-safe flag. false graduates the workspace (autonomous backlog dispatch + uncapped autonomy); absent leaves it unchanged.

boolean | null
quiet_hours_end

Quiet-window end (HH:MM, UTC). Absent = no change; null = clear.

string | null
quiet_hours_start

Quiet-window start (HH:MM, UTC). Absent = no change; null = clear.

string | null
required_worker_group_ids

ADR 0046 RUNG 1b — the workspace worker-group FLOOR binding (the always-unioned isolation primitive). Absent = no change; a value (incl. []) REPLACES the floor. Persisted only when present, so an unrelated settings patch never clears it. Workspace admin.

Array<string> | null
system_llm_credential
One of:
null
vision_model

#838 (B1) — the workspace’s VISION-model override (mig 357). DOUBLE-Option: absent = no change; null (or a blank string) = clear back to the instance default; a value SETS the model id used to describe images and, when a project arm pins no judge model of its own, to judge visual verification. Shape-validated only (a single token, bounded length) — an unusable id degrades at RESOLVE time rather than being rejected here, because the catalog is a remote document and a write must not fail on an upstream outage. Workspace-admin only.

string | null
voice_config

#980 (mig 408) — the workspace voice-stack config ({"mode": "web-only" | "hybrid" | "local-only", "asr"?, "streaming_asr"?, "tts"?, "llm"?}). DOUBLE-Option: absent = no change; null = clear (the deployment default applies); a value REPLACES it. The service validates the closed mode/provider sets and canonicalizes the value, so an unknown provider is a 400. Workspace-admin only.

Settings patched; the echo round-trips the persisted values

Media typeapplication/json

The 200 body of PATCH /api/workspaces/{id}/settings — the typed echo apply_settings_patch builds and returns. ok plus the request-echoed onboarding / quiet-hours fields are ALWAYS present (the quiet-hours pair echoes the request, so it is null when absent/cleared); every remaining block is Some(..) ONLY when the patch touched it and skip_serializing_if omits the key otherwise — so the wire shape carries exactly the fields the patch changed.

object
allow_debug

#583 Item 2 / #582 — echoed Debug-light CEILING — present ONLY when the patch touched it.

boolean | null
autonomy_level

Persisted autonomy level (0..=100, clamped to the plan ceiling) — present ONLY when the patch touched the autonomy fields.

integer | null format: int32
autonomy_overrides

Persisted per-axis override vector — present ONLY when the patch touched the autonomy fields.

calibration_carrier_preference
One of:
null
closeout_config

#712 — echoed workspace-tier standard-closeout config — present ONLY when the patch touched it (round-trips the persisted/validated config).

default_max_subagents

#838 — echoed workspace-default subagent ceiling — present ONLY when the patch touched it. Some(Some(n)) = set; Some(None) = cleared.

integer | null format: int32
default_resource_class
One of:
null
default_tool_approval_deny_minutes

#1175 — echoed workspace-default deny budget — present ONLY when the patch touched it. Some(Some(minutes)) = the persisted value; Some(None) = cleared. No canonicalisation is possible or needed: an integer has one spelling, and an out-of-range value never reaches here because the write refuses it with a 400 first.

integer | null format: int32
digest_cadence

Persisted digest cadence (off | daily | weekly) — present ONLY when the patch touched the digest config.

string | null
digest_enabled

Persisted digest-enabled flag — present ONLY when the patch touched the digest config.

boolean | null
environment_scope_floor

#708 W8 — echoed environment-scope FLOOR — present ONLY when the patch touched it. Some(Some(scope)) = the mandated floor; Some(None) = cleared (no floor).

string | null
failure_notify
One of:
null
harness_config

#1397 (mig 404) — the per-harness agent config bag — present ONLY when the patch touched it. Some(Some(bag)) = the persisted (validated) bag; Some(None) = cleared (no per-harness overrides).

mcp_external_ops_hourly_limit

#977 — echoed external-MCP OPS aggregate override — present ONLY when the patch touched it. Some(Some(n)) = set; Some(None) = cleared.

integer | null format: int32
mcp_external_read_hourly_limit

#977 — echoed external-MCP READ window override — present ONLY when the patch touched it. Some(Some(n)) = set; Some(None) = cleared.

integer | null format: int32
memory_embedding
One of:
null
memory_governance
One of:
null
model_fallback_chains

#926 — echoed quota-fallback chain override — present ONLY when the patch touched it. Some(Some(chains)) = the persisted (validated) override; Some(None) = cleared back to the built-in chains.

ok
required

Always true on a successful patch.

boolean
onboarding_mode

Echoed onboarding-safe flag (the request value; null when the field was absent from the request).

boolean | null
quiet_hours_end

Echoed quiet-window end (HH:MM, UTC); null when absent/cleared.

string | null
quiet_hours_start

Echoed quiet-window start (HH:MM, UTC); null when absent/cleared.

string | null
system_llm_credential
One of:
null
vision_model

#838 (B1) — echoed VISION-model override — present ONLY when the patch touched it. Some(Some(model)) = the persisted (normalized) id; Some(None) = cleared back to the instance default. The echo carries the NORMALIZED value, not the raw request, so a client that sent " gpt-5.6-luna " sees what was actually stored.

string | null
voice_config

#980 (mig 408) — the voice-stack config — present ONLY when the patch touched it. Some(Some(config)) = the persisted (canonicalized) config; Some(None) = cleared.

Example
{
"calibration_carrier_preference": "subscription_first",
"default_resource_class": "normal"
}

Structured client error

Media typeapplication/json

The canonical JSON body of every error response — the single source of truth the frontend binds to. Every AppError serializes as this exact shape, and the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is what the frontend error schema is generated from, so there is no hand-written error schema on either end.

object
code
required

Machine-readable, stable error code.

string
Allowed values: not_found unauthorized forbidden license_required license_expired bad_request unprocessable precondition_failed conflict method_not_allowed rate_limited too_many_requests quota_exceeded database_error docker_error vault_error internal_error
details
One of:
null
error
required

Human-readable message (the server’s English text; the client may localize by code).

string
Example
{
"code": "not_found"
}

Authentication required

Media typeapplication/json

The canonical JSON body of every error response — the single source of truth the frontend binds to. Every AppError serializes as this exact shape, and the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is what the frontend error schema is generated from, so there is no hand-written error schema on either end.

object
code
required

Machine-readable, stable error code.

string
Allowed values: not_found unauthorized forbidden license_required license_expired bad_request unprocessable precondition_failed conflict method_not_allowed rate_limited too_many_requests quota_exceeded database_error docker_error vault_error internal_error
details
One of:
null
error
required

Human-readable message (the server’s English text; the client may localize by code).

string
Example
{
"code": "not_found"
}

Permission denied

Media typeapplication/json

The canonical JSON body of every error response — the single source of truth the frontend binds to. Every AppError serializes as this exact shape, and the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is what the frontend error schema is generated from, so there is no hand-written error schema on either end.

object
code
required

Machine-readable, stable error code.

string
Allowed values: not_found unauthorized forbidden license_required license_expired bad_request unprocessable precondition_failed conflict method_not_allowed rate_limited too_many_requests quota_exceeded database_error docker_error vault_error internal_error
details
One of:
null
error
required

Human-readable message (the server’s English text; the client may localize by code).

string
Example
{
"code": "not_found"
}

Structured server error

Media typeapplication/json

The canonical JSON body of every error response — the single source of truth the frontend binds to. Every AppError serializes as this exact shape, and the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is what the frontend error schema is generated from, so there is no hand-written error schema on either end.

object
code
required

Machine-readable, stable error code.

string
Allowed values: not_found unauthorized forbidden license_required license_expired bad_request unprocessable precondition_failed conflict method_not_allowed rate_limited too_many_requests quota_exceeded database_error docker_error vault_error internal_error
details
One of:
null
error
required

Human-readable message (the server’s English text; the client may localize by code).

string
Example
{
"code": "not_found"
}