opbox

The user.* family (6 verbs). Every verb enters through the one front door: it is capability-checked, permission-checked, scope-checked and audited before it runs (see Security & permissions). Each entry below lists its capability, risk class (which fixes the minimum autonomy level), the minimum caller permission, its availability tier, whether it is idempotent, and any outbound egress.

Clearuser.aiConfig.clear

PropertyValue
Capabilityactor.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

Delete the acting user’s personal config for the active workspace. Idempotent (0 rows -> cleared

).

Call POST /v/user.aiConfig.clear with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool user.aiConfig.clear.

Setuser.aiConfig.set

PropertyValue
Capabilityactor.write
Risk classSensitive - autonomy L2
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

Upsert by (user_id, workspace_id). Only PRESENT fields are written (CASE-WHEN flags); absent fields are left unchanged on update / NULL on create. clearKeyVerifiedAt (set by the route when an API key rotates) nulls key_verified_at so the verify-ai-keys cron re-probes. The org allowPersonalKeys policy gate + activeCredentialId parent-org validation stay in the route (defense-in-depth).

Call POST /v/user.aiConfig.set with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool user.aiConfig.set.

Updateuser.profile.update

PropertyValue
Capabilityactor.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

Patch the acting user’s own profile columns (name / image / preferences). Absent fields unchanged. SELF only (id = the acting users.id) - it ignores any caller-supplied target id, so it can never edit another user’s row (the admin user-edit + role-grant sites are court-gated / deferred, not served here).

Call POST /v/user.profile.update with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool user.profile.update.

Createuser.theme.create

PropertyValue
Capabilityactor.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

Insert a new theme for the acting user. When isActive, deactivate the user’s other themes in the SAME tx first. Unique (“userId”, name) -> 23505 mapped to a 409 Conflict (the route’s prior P2002 -> 409 “A theme with this name already exists”).

Call POST /v/user.theme.create with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool user.theme.create.

Deleteuser.theme.delete

PropertyValue
Capabilityactor.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

Ownership-fenced hard delete of one of the acting user’s themes.

Call POST /v/user.theme.delete with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool user.theme.delete.

Updateuser.theme.update

PropertyValue
Capabilityactor.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

Ownership-fenced (themeId must resolve to the acting user). Optional name / tokens (FULL REPLACE - the route already read-merges and passes the final object) / isActive (when true, deactivate the user’s other themes in the SAME tx - folds the route’s separate deactivate-siblings updateMany). Absent fields are left unchanged (CASE-WHEN flags).

Call POST /v/user.theme.update with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool user.theme.update.