# Capabilities

> The Capabilities operations of the Hub REST API (14 operations).

URL: https://docs.synap.live/docs/reference/hub-api/capabilities · Generated from Synap-core/backend@c31e261ab (main)

Paths are relative to `/api/hub` on a pod.

## GET /capabilities

List the capability read-model (verb × grant × approval matrix)

Returns the unified `Capability[]` read-model for a workspace (pod-wide + the given workspace): tools + skills + commands, each with `verbs` (label/kind/granted/effectiveExecMode/govDefault), `governance` (the run posture for an agent: auto runs now, propose files a review, none nothing runs here) and `enabled` (the approval gate — a separate fact). Reuses the same `listCapabilities` adapter the tRPC `playbooks.capabilityRegistry.list` exposes. Requires hub-protocol.read scope. Pass `workspaceId` to scope to one workspace; OMIT it for the pod-wide read (all accessible workspaces + globals, deduped). Pass `intent` (a registry slug) to keep only the capabilities declaring at least one verb with that vendor-independent routing intent — applied AFTER the pod-wide merge, so a capability visible through two workspaces is judged once. A verb declaring no intent never matches.

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `workspaceId` | query | string |  |  |
| `intent` | query | string |  |  |

Responses:

- `200`: Capability read-model
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## GET /capabilities/containers

List capability CONTAINERS (named bundles) the agent can discover

Returns the capability containers visible in a workspace (pod-wide + the given workspace). Each container is a named bundle of parts — Connections (tools), Skills, and Built-ins — grouped via the `links` table as `tool|skill --member_of--> capability`. Each returned container includes its `parts` (connections/builtins/skills with names + kinds) so the agent knows what each bundle contains. Thin door over the tRPC `capabilities.containers.list` + `.get` callers. Pass `query` to SEARCH (ranked match over container name/description + member names) instead of the full dump; `limit` caps the result count (default 20 with a query). No `kind` filter here — a container is a bundle, not a single-kind verb (use the plain `/capabilities` list's `kind` filter for that axis). Requires hub-protocol.read scope. Pass `workspaceId` to scope to one workspace; OMIT it for the pod-wide read (all accessible workspaces + globals, deduped). Pass `intent` (a registry slug) to keep only the containers holding at least one MEMBER brick that declares that vendor-independent routing intent — the intent lives on the verb, so the match is resolved against the capability REGISTRY and mapped back through each brick's `containerId`. A container whose members declare no intent never matches.

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `workspaceId` | query | string |  |  |
| `query` | query | string |  |  |
| `intent` | query | string |  |  |
| `limit` | query | integer |  |  |

Responses:

- `200`: Capability containers with their member parts
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## POST /capabilities/apply

Apply a capability template

Instantiates a set of {vault secrets · tools · skills} from a config descriptor (inline `definition` or a seed `templateKey`) + `params`. `{{param}}` placeholders are interpolated; each tool/skill is created through the governed routers (proposal-gated — proposal ids are surfaced in `proposals`). Requires hub-protocol.write scope.

Request body (`application/json`): object

| Field | Type | Required | Description |
|---|---|---|---|
| `definition` | object |  |  |
| `templateKey` | string |  |  |
| `params` | object |  |  |
| `workspaceId` | string |  |  |

Responses:

- `200`: Applied capability (created + proposed resources)
- `400`: Bad request
- `403`: Forbidden
- `404`: Template not found
- `412`: Setup required — a human must supply a param or connect
- `500`: Internal error

## POST /capabilities/reconcile

Reconcile installed capabilities to their Control-Plane templates

Re-projects drifted capability-template skills (providerSpec/parameters/code/description) onto their installed containers through the governed `createCapabilityFromDefinition` applier — add-only, never touches a skill the template doesn't declare. `dryRun:true` computes the report without writing. Requires hub-protocol.write scope.

Request body (`application/json`): object

| Field | Type | Required | Description |
|---|---|---|---|
| `dryRun` | boolean |  |  |

Responses:

- `200`: Reconcile report
- `403`: Forbidden
- `500`: Internal error

## POST /configs/reconcile

Reconcile standalone-installed views/skills/automations to their templates

Advances each standalone-installed view/skill/automation's untouched fields to its source marketplace template through the governed per-kind `.update` door. A user-edited field is owner-owned — left alone and reported. A private/unsynced package is skipped. `dryRun:true` computes the report without writing. Requires hub-protocol.write scope.

Request body (`application/json`): object

| Field | Type | Required | Description |
|---|---|---|---|
| `dryRun` | boolean |  |  |

Responses:

- `200`: Reconcile report
- `403`: Forbidden
- `500`: Internal error

## POST /configs/detach

Detach a view/skill/automation from its marketplace template

Clears the `marketSource` source-link on a standalone-installed view/skill/automation so a future template change no longer reconciles onto it. Persisted through the governed per-kind `.update` door. Requires hub-protocol.write scope.

Request body (`application/json`): object

| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | "view" \| "skill" \| "automation" | yes |  |
| `id` | string | yes |  |

Responses:

- `200`: Detach result
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## DELETE /capabilities/containers/{id}

Delete a capability container by id (member parts untouched)

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes |  |

Responses:

- `200`: Deleted
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## GET /capabilities/catalog

Pack-grouped, status-computed capability catalog (one row per pack)

Returns ONE `CapabilityCard` per capability PACK: each installed container (member tools folded into `connection`, member skills into `verbs`) plus each available template not yet installed. Every card carries a computed `status` (available / needs_connection / connected / draft / ready / partial / unavailable) and exactly one `nextAction`. De-duped by pack identity (the container/template name) so duplicate bare tools/skills collapse under their pack. Requires hub-protocol.read scope and a `workspaceId` query param.

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `workspaceId` | query | string | yes |  |
| `key` | query | string |  |  |

Responses:

- `200`: Capability catalog (pack cards)
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## GET /capabilities/actions

List only actions an external AI can execute now

Projects the shared capability registry into executable actions. Draft, disconnected, teaching-only, and catalog-only entries are omitted. Each action carries its real parameter schema, connection state, approval posture, and effective execution mode. Requires a workspace lens. Pass `intent` (a closed ABSTRACT_VERBS value) to keep only the actions whose verb declares that routing intent — the vendor-independent way to ask "what can send a message?". A verb that declares no intent is never matched (the vocabulary is closed; absence is never guessed).

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `workspaceId` | query | string | yes |  |
| `query` | query | string |  |  |
| `kind` | query | string |  |  |
| `intent` | query | string |  |  |
| `limit` | query | integer |  |  |

Responses:

- `200`: Runnable actions
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## POST /capabilities/execute

Execute a registered capability (agnostic capability launcher)

Resolves a capability verb (verbId = backing skill name) or a skillId to its skill, runs it through gateCapabilityExecution, and on `run` delegates to the IS sandbox executor. Returns 200 with the result (or a dry-run preview), 202 with a reviewable proposal when governance requires approval, 403 when denied. Requires hub-protocol.write scope.

Request body (`application/json`): ExecuteCapabilityRequest

| Field | Type | Required | Description |
|---|---|---|---|
| `verbId` | string |  | The capability verb to run — the backing skill's NAME (e.g. 'gmail_send'). One of verbId/skillId is required. |
| `skillId` | string |  | Direct backing-skill id (alternative to verbId). |
| `parameters` | object |  | The capability's own inputs, passed to the skill sandbox as `args` — e.g. { to, subject, body } for gmail_send. |
| `workspaceId` | string |  | Acting workspace — an optional lens narrowing the skill lookup and the gate. Omit for a pod-wide run. |
| `connectionSelector` | object |  | Which of the capability's connections this run uses, when it has more than one. A selector matching nothing fails the run. |
| `idempotencyKey` | string |  | A stable key correlating retries of THIS logical run, so an external-send verb is at-most-once. Omitted → a content hash is derived. |
| `channelId` | string |  | Instruction-provenance: the acting channel of this agent turn. A run triggered from an untrusted-origin channel force-proposes instead of auto-running. |
| `sourceMessageId` | string |  | The triggering inbound message id, resolved server-side to the acting channel when channelId is not passed explicitly. |
| `sessionId` | string |  | The focus session this run belongs to. Persisted as the proposals.session_id column so a reviewer sees which operation the action was part of. |

Responses:

- `200`: Execution result
- `202`: Execution requires human approval — a reviewable proposal was created instead of running.
- `400`: Bad request
- `403`: Forbidden / denied by gate
- `404`: Capability/skill not found
- `424`: Failed dependency — a client-actionable connection failure (auth expired/absent, or a missing target). Body carries errorClass + providerRef so the caller can offer a reconnect path.
- `500`: Internal error

## GET /capabilities/{capabilityId}/credentials

List a capability's credentials

Returns metadata for the capability's credentials (vault rows carrying capability_id). NEVER returns secret values. Owner-scoped. Requires hub-protocol.read.

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `capabilityId` | path | string | yes |  |

Responses:

- `200`: Credentials
- `403`: Forbidden
- `404`: Not found
- `500`: Internal error

## POST /capabilities/{capabilityId}/credentials

Add a credential to a capability

Server-encrypts and stores a new credential (secrets row) for the capability. Promotes it to default when requested or when it is the capability's first credential. Requires hub-protocol.write.

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `capabilityId` | path | string | yes |  |

Request body (`application/json`): object

| Field | Type | Required | Description |
|---|---|---|---|
| `label` | string | yes |  |
| `value` | string |  |  |
| `contextType` | string \| null |  |  |
| `contextId` | string \| null |  |  |
| `accountHint` | string \| null |  |  |
| `isDefault` | boolean |  |  |
| `isPodWide` | boolean |  |  |

Responses:

- `200`: Created credential
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## PATCH /capabilities/{capabilityId}/credentials/{id}

Update a capability credential

Updates credential fields; rotates (re-encrypts) when `value` is given; enforces a single default. Requires hub-protocol.write.

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `capabilityId` | path | string | yes |  |
| `id` | path | string | yes |  |

Request body (`application/json`): object

| Field | Type | Required | Description |
|---|---|---|---|
| `label` | string |  |  |
| `value` | string |  |  |
| `contextType` | string \| null |  |  |
| `contextId` | string \| null |  |  |
| `accountHint` | string \| null |  |  |
| `isDefault` | boolean |  |  |
| `isPodWide` | boolean |  |  |

Responses:

- `200`: Updated credential
- `400`: Bad request
- `403`: Forbidden
- `404`: Not found
- `500`: Internal error

## DELETE /capabilities/{capabilityId}/credentials/{id}

Remove a capability credential

Soft-deletes a credential; promotes the oldest remaining credential to default when the removed one was default. Requires hub-protocol.write.

Parameters:

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `capabilityId` | path | string | yes |  |
| `id` | path | string | yes |  |

Responses:

- `200`: Removed
- `403`: Forbidden
- `404`: Not found
- `500`: Internal error
