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`).
const url = 'https://example.com/api/workspaces/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/settings';const options = { method: 'PATCH', headers: { cookie: 'supacloud_session=<supacloud_session>', 'Content-Type': 'application/json' }, body: '{"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"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}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" }'Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Workspace ID whose settings to patch
Request Bodyrequired
Section titled “Request Bodyrequired”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
#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.
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.
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.
#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).
#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.
#1176 (mig 361) — the workspace-DEFAULT resource class. DOUBLE-Option:
absent = no change; null = clear the default (the built-in normal
applies unless a profile or the launch names one); a value SETS it
(normal | heavy, else 400). Workspace-admin only.
The value is stored CANONICALISED (trimmed, lowercased), because the launch
fold and the concurrency SQL both compare against the canonical wire string
— a stored "Heavy" would pass this write and then silently never match.
#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.
#519 #11 — the digest cadence (off | daily | weekly). Absent leaves it
unchanged. An invalid value is a 400.
#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).
#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.
#838 — the workflow-failure alert target (mig 319). DOUBLE-Option: absent =
no change; null = disarm alerting; a value SETS the target (the resource
must be a telegram_bot in THIS workspace, the chat id non-empty — an
invalid target is a 400). Workspace-admin only.
object
The channel the alert is sent to — a Telegram chat id (numeric or
@channel) or a Discord channel snowflake, per the resource’s kind.
The chat resource whose bot token sends the alert. #1052: a telegram_bot
OR a discord resource — the transport follows this resource’s kind, so
the alert reaches whichever chat surface the workspace works in.
#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.
#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.
#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.
#1228 slice 2 — the memory-EMBEDDING config (mig 368). Absent = no change; null
= clear back to full auto; an object {provider?, model?} = set (normalized +
validated: a provider must have an embeddings API, a model is one token). An
all-empty object clears too. Workspace-admin only.
object
ADR 0049 P3 / Tier-3 (#547/#548) — the per-scope memory GOVERNANCE policy (auto-publish / require-review / retention-days, keyed by workspace|project|personal). Absent leaves the stored policy unchanged; a value REPLACES it (the panel always sends the full three-scope policy). Validated (no contradictory auto-publish + require-review; bounded retention) before persist. Workspace admin.
object
The governance rule for ONE memory scope: whether new memories of that scope skip
the review queue (auto_publish), whether they must be human-reviewed
(require_review), and how long they are retained before the decay sweep may
archive them (retention_days, 0 = keep forever).
auto_publish and require_review are intentionally NOT collapsed into one flag:
the conservative default is “review required, not auto-published”, and a future
“auto-published BUT still logged for review” mode keeps both axes meaningful. The
validation below rejects the contradictory auto_publish && require_review combo.
object
New memories of this scope skip the review queue and land live immediately.
New memories of this scope must be human-reviewed before they go live.
Days a memory of this scope is retained before the decay sweep may archive it;
0 keeps it forever. Defaults per scope: shared scopes (workspace/project)
start at [DEFAULT_SHARED_RETENTION_DAYS] (#1228), personal at 0.
not matching
The governance rule for ONE memory scope: whether new memories of that scope skip
the review queue (auto_publish), whether they must be human-reviewed
(require_review), and how long they are retained before the decay sweep may
archive them (retention_days, 0 = keep forever).
auto_publish and require_review are intentionally NOT collapsed into one flag:
the conservative default is “review required, not auto-published”, and a future
“auto-published BUT still logged for review” mode keeps both axes meaningful. The
validation below rejects the contradictory auto_publish && require_review combo.
object
New memories of this scope skip the review queue and land live immediately.
New memories of this scope must be human-reviewed before they go live.
Days a memory of this scope is retained before the decay sweep may archive it;
0 keeps it forever. Defaults per scope: shared scopes (workspace/project)
start at [DEFAULT_SHARED_RETENTION_DAYS] (#1228), personal at 0.
not matching
The governance rule for ONE memory scope: whether new memories of that scope skip
the review queue (auto_publish), whether they must be human-reviewed
(require_review), and how long they are retained before the decay sweep may
archive them (retention_days, 0 = keep forever).
auto_publish and require_review are intentionally NOT collapsed into one flag:
the conservative default is “review required, not auto-published”, and a future
“auto-published BUT still logged for review” mode keeps both axes meaningful. The
validation below rejects the contradictory auto_publish && require_review combo.
object
New memories of this scope skip the review queue and land live immediately.
New memories of this scope must be human-reviewed before they go live.
Days a memory of this scope is retained before the decay sweep may archive it;
0 keeps it forever. Defaults per scope: shared scopes (workspace/project)
start at [DEFAULT_SHARED_RETENTION_DAYS] (#1228), personal at 0.
not matching
#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-safe flag. false graduates the workspace (autonomous backlog
dispatch + uncapped autonomy); absent leaves it unchanged.
Quiet-window end (HH:MM, UTC). Absent = no change; null = clear.
Quiet-window start (HH:MM, UTC). Absent = no change; null = clear.
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.
#532 (A5) — the OWNER-pinned credential that backs INTERNAL system-prompt LLM
calls (backlog classify / judge / recommendations / debate / vision). DOUBLE-
Option: absent = no change; null = clear the pin (back to the default
most-specific precedence); a value PINS the credential by id + kind. Validated
to be visible to the workspace before persist (never trust the id for
cross-workspace access).
object
The credential’s primary key (an api_key_refs.id or agent_oauth_tokens.id).
The credential kind: api_key or oauth_token.
#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.
#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.
Responses
Section titled “Responses”Settings patched; the echo round-trips the persisted values
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
#583 Item 2 / #582 — echoed Debug-light CEILING — present ONLY when the patch touched it.
Persisted autonomy level (0..=100, clamped to the plan ceiling) — present
ONLY when the patch touched the autonomy fields.
Persisted per-axis override vector — present ONLY when the patch touched the autonomy fields.
#712 — echoed workspace-tier standard-closeout config — present ONLY when the patch touched it (round-trips the persisted/validated config).
#838 — echoed workspace-default subagent ceiling — present ONLY when the
patch touched it. Some(Some(n)) = set; Some(None) = cleared.
#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.
Persisted digest cadence (off | daily | weekly) — present ONLY when the
patch touched the digest config.
Persisted digest-enabled flag — present ONLY when the patch touched the digest config.
#708 W8 — echoed environment-scope FLOOR — present ONLY when the patch touched it.
Some(Some(scope)) = the mandated floor; Some(None) = cleared (no floor).
#838 — echoed workflow-failure alert target — present ONLY when the patch
touched it. Some(Some(target)) = armed; Some(None) = disarmed.
object
The channel the alert is sent to — a Telegram chat id (numeric or
@channel) or a Discord channel snowflake, per the resource’s kind.
The chat resource whose bot token sends the alert. #1052: a telegram_bot
OR a discord resource — the transport follows this resource’s kind, so
the alert reaches whichever chat surface the workspace works in.
#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).
#977 — echoed external-MCP OPS aggregate override — present ONLY when the
patch touched it. Some(Some(n)) = set; Some(None) = cleared.
#977 — echoed external-MCP READ window override — present ONLY when the
patch touched it. Some(Some(n)) = set; Some(None) = cleared.
ADR 0049 P3 / Tier-3 — echoed per-scope memory governance policy — present ONLY when the patch touched it (the panel always sends the full three-scope policy, so the echo round-trips the persisted/validated policy).
object
The governance rule for ONE memory scope: whether new memories of that scope skip
the review queue (auto_publish), whether they must be human-reviewed
(require_review), and how long they are retained before the decay sweep may
archive them (retention_days, 0 = keep forever).
auto_publish and require_review are intentionally NOT collapsed into one flag:
the conservative default is “review required, not auto-published”, and a future
“auto-published BUT still logged for review” mode keeps both axes meaningful. The
validation below rejects the contradictory auto_publish && require_review combo.
object
New memories of this scope skip the review queue and land live immediately.
New memories of this scope must be human-reviewed before they go live.
Days a memory of this scope is retained before the decay sweep may archive it;
0 keeps it forever. Defaults per scope: shared scopes (workspace/project)
start at [DEFAULT_SHARED_RETENTION_DAYS] (#1228), personal at 0.
not matching
The governance rule for ONE memory scope: whether new memories of that scope skip
the review queue (auto_publish), whether they must be human-reviewed
(require_review), and how long they are retained before the decay sweep may
archive them (retention_days, 0 = keep forever).
auto_publish and require_review are intentionally NOT collapsed into one flag:
the conservative default is “review required, not auto-published”, and a future
“auto-published BUT still logged for review” mode keeps both axes meaningful. The
validation below rejects the contradictory auto_publish && require_review combo.
object
New memories of this scope skip the review queue and land live immediately.
New memories of this scope must be human-reviewed before they go live.
Days a memory of this scope is retained before the decay sweep may archive it;
0 keeps it forever. Defaults per scope: shared scopes (workspace/project)
start at [DEFAULT_SHARED_RETENTION_DAYS] (#1228), personal at 0.
not matching
The governance rule for ONE memory scope: whether new memories of that scope skip
the review queue (auto_publish), whether they must be human-reviewed
(require_review), and how long they are retained before the decay sweep may
archive them (retention_days, 0 = keep forever).
auto_publish and require_review are intentionally NOT collapsed into one flag:
the conservative default is “review required, not auto-published”, and a future
“auto-published BUT still logged for review” mode keeps both axes meaningful. The
validation below rejects the contradictory auto_publish && require_review combo.
object
New memories of this scope skip the review queue and land live immediately.
New memories of this scope must be human-reviewed before they go live.
Days a memory of this scope is retained before the decay sweep may archive it;
0 keeps it forever. Defaults per scope: shared scopes (workspace/project)
start at [DEFAULT_SHARED_RETENTION_DAYS] (#1228), personal at 0.
not matching
#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.
Always true on a successful patch.
Echoed onboarding-safe flag (the request value; null when the field was
absent from the request).
Echoed quiet-window end (HH:MM, UTC); null when absent/cleared.
Echoed quiet-window start (HH:MM, UTC); null when absent/cleared.
Echoed system-LLM credential pin ({id,kind}) or null when cleared —
present ONLY when the patch touched the pin. DOUBLE-Option: the OUTER layer
gates presence (absent ⇒ key omitted), the INNER carries Some(pin) (echoes
{id,kind}) vs None (echoes null, i.e. the pin was cleared back to the
default precedence). Mirrors the apply path’s three-state echo; utoipa flattens
the nested Option into a single nullable SystemLlmCredentialPinEcho.
object
The pinned credential’s primary key.
The credential kind: api_key or oauth_token.
#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.
#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
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
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}Authentication required
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
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}Permission denied
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
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}Structured server error
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
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}