---
title: "Change an installed skill's sharing scope"
method: PATCH
path: "/v1/skill-installations/{installation_id}/visibility"
tags: ["skill-hub"]
---

# Change an installed skill's sharing scope

`PATCH /v1/skill-installations/{installation_id}/visibility`

## Request

- Base URL: `https://api.patricia.app` (the document declares no server; this is the origin it was published from)
- URL: `https://api.patricia.app/v1/skill-installations/{installation_id}/visibility`
- Auth: HTTP bearer

Move one installed skill between TEAM (everyone in the workspace can use it) and PERSONAL (only the person who took it). Requires skills:install. ``expected_current`` is a compare-and-set guard: a change that landed first returns 409 instead of clobbering it, and a request whose two scopes are equal is refused (422) rather than silently reassigning ownership. Only an install whose runtime readers enforce ownership can be personal: an ``instructions`` install of any provenance, or a ``skill`` one whose provenance a run can load (git-catalog, platform-admin, or a skill this workspace wrote itself); a ``sans`` install stays shared (400), and so does an install that came from a bundle (400). Sharing a personal install back with the team is always accepted, so read ``can_change_visibility`` on the install rather than deriving either rule again. Another member's personal install is never disclosed: it 404s like a missing one, for admins too. One exception, and it is a DISCLOSURE rule rather than a move rule: an install whose personal owner can no longer reach it is shown to a skills:edit holder so the workspace can recover it, so a caller who may install but not edit gets the same 404 for those rows that everyone else does.

## Path parameters

- `installation_id` string, uuid, required

## Headers

- `X-Tenant-Id` string, nullable

## Request body

- InstallationVisibilityBody: Move one installed skill between TEAM (the whole workspace) and PERSONAL (just its owner). ``expected_current`` is a compare-and-set guard: the write lands only while the install is still at that scope, so a change that raced this one surfaces a conflict instead of silently clobbering. The two values must DIFFER: a same-scope request is refused rather than treated as a harmless no-op, because the only thing such a write could do is reassign ownership.
  - `expected_current` 'team' | 'personal', required: Who, within a tenant, an installed skill is visible to (``skill_installation.visibility_code``). The skill-side counterpart of ``IntegrationVisibility``: ``TEAM`` is any actor in the tenant, ``PERSONAL`` is only the actor who installed it (``owner_actor_id``). A TEAM install is safe to surface in the shared, per-tenant prompt cache; a PERSONAL install is surfaced only through the per-run, actor-scoped progressive-disclosure loader (never the shared prompt, which would leak it across actors).
  - `target` 'team' | 'personal', required: Who, within a tenant, an installed skill is visible to (``skill_installation.visibility_code``). The skill-side counterpart of ``IntegrationVisibility``: ``TEAM`` is any actor in the tenant, ``PERSONAL`` is only the actor who installed it (``owner_actor_id``). A TEAM install is safe to surface in the shared, per-tenant prompt cache; a PERSONAL install is surfaced only through the per-run, actor-scoped progressive-disclosure loader (never the shared prompt, which would leak it across actors).

## Response `200`

Successful Response

- InstallationSummary: A tenant's skill installation: the Hub card + settings header (no PII). Carries the installed skill's display metadata (``title``/``slug``/``description``) resolved from the pinned manifest version's ``SkillDefinition`` so the Hub can name the card + page header without a second lookup. These are display labels, never a personal fact (G4). Every response uses the shared installation-summary builder, which also supplies the definition's listing state.
  - `id` string, uuid, required
  - `skill_id` string, uuid, required
  - `manifest_version_id` string, uuid, required
  - `status_code` string, required
  - `config_version` integer, required
  - `projection_generation` integer, required
  - `activated_at` string, date-time, nullable, required
  - `first_live_at` string, date-time, nullable, required
  - `killed_at` string, date-time, nullable, required
  - `kill_reason` string, nullable, required
  - `title` string, nullable
  - `slug` string, nullable
  - `description` string, nullable
  - `listing_state` 'unlisted' | 'testing' | 'listed': Who can discover and install a global skill.
  - `is_portable` boolean, nullable
  - `archetype_code` string, nullable
  - `can_edit_definition` boolean, nullable
  - `removable` boolean: Whether this installation may be removed from the workspace.
  - `visibility_code` 'team' | 'personal', required: Who, within a tenant, an installed skill is visible to (``skill_installation.visibility_code``). The skill-side counterpart of ``IntegrationVisibility``: ``TEAM`` is any actor in the tenant, ``PERSONAL`` is only the actor who installed it (``owner_actor_id``). A TEAM install is safe to surface in the shared, per-tenant prompt cache; a PERSONAL install is surfaced only through the per-run, actor-scoped progressive-disclosure loader (never the shared prompt, which would leak it across actors).
  - `is_mine` boolean, nullable
  - `can_change_visibility` boolean
  - `has_settings` boolean, nullable
  - `is_catalog_default` boolean
  - `effectively_enabled` boolean
  - `update_available` boolean
  - `available_version_id` string, uuid, nullable
  - `outdated_config` boolean
  - `outdated_config_reasons` string[]
  - `about` SkillAboutSummary: What a skill IS, projected from its pinned manifest: the Hub's plain-language overview. The Hub renders a skill's feature from its compiled ``feature_surface`` rows, but a skill whose manifest declares no surfaces (every ``skill``/``instructions`` archetype today) compiles none, so that path alone leaves the page blank. The manifest still describes the skill perfectly well (what it is for, what to say to reach it, what it wants connected), and this projects exactly that, generically: no per-skill branching, one shape for every archetype. Display labels only, never a personal fact (G4). Every field is optional-or-empty so a manifest that omits one simply renders one section fewer.
    - `category` string, nullable
    - `taxonomy` string, nullable
    - `example_phrases` string[]
    - `requires_connected` string[]
    - `works_with` string[]
    - `needs_skills` string[]
    - `credits_per_run` integer, nullable
    - `instructions_body` string, nullable
    - `has_settings` boolean

## Other responses

- `400`: Bad request
- `401`: Unauthorized
- `403`: Forbidden. Inspect response body `code` for the specific reason (for example: PERMISSION_DENIED, EMAIL_NOT_VERIFIED, TENANT_ACCESS_REQUIRED, ROLE_UNRESOLVED)
- `404`: Not found
- `409`: Conflict
- `422`: Validation error

---

[API](https://skmtc.dev/patricia/apis/patricia-api.md) · [All operations](https://skmtc.dev/patricia/apis/patricia-api/llms.txt) · [OpenAPI document](https://skmtc.dev/patricia/apis/patricia-api/revisions/0dfb44495ea1?raw)
