Save a new version of a workspace profile
Owners and admins (workspace.manage), signed in. Narrows the operator profile base_profile_id: models and tools are subsets of the base's, and permission_mode the base's or one that leaves more to a person. Anything wider is 400 with details.field. Each save makes the next version of name with a new id (ws_<workspace slug>_<name>_v<n>); earlier versions, and what already runs on them, stay as they were. Two saves of one name at once: the second is 409 REVISION_CONFLICT, and saving again makes the version after. Instead of tools, deny names the catalog tools to block on top of the base's: the base's tools less deny are saved, and a deny that leaves out a tool the base blocks is 400 base_denies; the other deny and accept_added_tools refusals are those of POST /v1/installation-profiles, weighed against the latest version of name. A version keeps every mcp__ block of the one before; unblock_mcp_tools lifts a held one (refusals as for POST /v1/installation-profiles) and needs previous_id, the version the form read: another latest version is 409 REVISION_CONFLICT (details.reason_code: stale_previous, details.latest_id). Writes a create_workspace_profile receipt.
Auth: cookie session only; no scope. Idempotency-Key: ignored.
cookieSession__Host-werft_session<token>For the web console: the browser session cookie set by POST /v1/auth/login. It holds the scopes of the user's workspace role (owner: all; admin and member: all but sessions:recover). Requests other than GET must also send X-Requested-With: werft-web.
X-Requested-With?"werft-web"Required with a cookie session: the literal werft-web.
"werft-web"application/json- body
name*string^[a-z0-9][a-z0-9-]{0,39}$base_profile_id*string1 <= length <= 200model*string1 <= length <= 200models*array<>1 <= items <= 32tools?array<>The built-in tools sessions run, kept as given. Exactly one of tools and deny.
items <= 64deny?array<>The built-in tools of the runtime version's tool catalog to block, and mcp__<server>__<tool> names of the servers this installation lists (not the built-in github server); the server stores the rest of the catalog as tools. A derived version names every mcp__ name its base blocks; a next version keeps those of the one before. [] blocks none. Exactly one of tools and deny; 400 for a runtime version with no catalog.
items <= 64accept_added_tools?array<>With deny: catalog tools the previous version never weighed that this one may run. Any such tool neither here nor in deny is 400.
items <= 64unblock_mcp_tools?array<>mcp__<server>__<tool> names the previous version blocked that this next version no longer blocks: the only way to lift one, as a version otherwise keeps every mcp__ name of the one before. Each must be held by the previous version (400 mcp_unblock_unknown), not named in deny too (400 mcp_unblock_conflict) and not blocked by the base (400 base_mcp_deny_dropped); a version with no previous one cannot lift any (400 mcp_unblock_no_previous). A workspace version names its previous version in previous_id.
items <= 64permission_mode*string"default""acceptEdits""dontAsk""plan""bypassPermissions""auto"previous_id?string1 <= length <= 200The version saved, and its receipt.
application/json- response
profile*receipt_id*|nullcurl -X POST 'https://example.com/v1/workspace-profiles' \ -H 'Content-Type: application/json' \ -d '{ "name": "strict-review", "base_profile_id": "claude-coding-real", "model": "claude-sonnet-5", "models": [ "claude-sonnet-5" ], "tools": [ "Read", "Glob", "Grep" ], "permission_mode": "default"}'{ "profile": { "id": "ws_acme_strict-review_v2", "source": "workspace", "model": "claude-sonnet-5", "models": [ "claude-sonnet-5" ], "tools": [ "Read", "Glob", "Grep" ], "permission_mode": "default", "runtime_kind": "claude_agent_sdk", "runtime_version": "0.3.319", "billing": "metered", "repository_mode": "required", "workspace": { "name": "strict-review", "version": 2, "base_profile_id": "claude-coding-real", "state": "usable", "created_by": "019a0000-0000-7000-8000-0000000000b1", "created_at": "2026-10-27T01:00:00.000Z", "retired_by": null, "retired_at": null } }, "receipt_id": "019a0000-0000-7000-8000-0000000000e1"}