> ## Documentation Index
> Fetch the complete documentation index at: https://docs.waterr.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Skill Packages

> Ship a skill as one file — instructions, tools, screen renderers and an events endpoint — and install it into a workspace in one call.

A **skill package** is a `waterr-skill.json` manifest. Installing it creates
everything the agent needs — a [meeting skill](https://docs.waterr.ai/api-reference/skills.md), its
[tools](https://docs.waterr.ai/api-reference/custom-functions.md), its [screen renderers](https://docs.waterr.ai/api-reference/screen-apps.md)
and a [webhook endpoint](https://docs.waterr.ai/api-reference/webhooks.md) for the events it cares about —
as ordinary rows your workspace already knows how to run. Attach the install to a
scenario and the agent has the skill on its next call.

```bash theme={null}
# 1. register the manifest
curl -X POST https://api.waterr.ai/v1/skill-packages \
  -H "Authorization: Bearer wai_live_xxx" -H "Content-Type: application/json" \
  -d '{ "url": "https://skills.acme.com/waterr-skill.json" }'

# 2. install it (per-install config + secrets)
curl -X POST https://api.waterr.ai/v1/skill-installs \
  -H "Authorization: Bearer wai_live_xxx" -H "Content-Type: application/json" \
  -d '{ "package_id": "acme.pricing_assistant", "config": { "price_book_id": "pb_2026" }, "secrets": { "ACME_API_KEY": "…" } }'

# 3. attach to a scenario
curl -X POST https://api.waterr.ai/v1/scenarios/{scenario_id}/skill-installs/{install_id}/attach \
  -H "Authorization: Bearer wai_live_xxx"
```

## The manifest

```jsonc theme={null}
{
  "schema": "waterr.skill/v1",
  "id": "acme.pricing_assistant",            // reverse-DNS, 3–64 chars, lowercase
  "version": "1.2.0",                         // semver; installs pin a version
  "name": "Pricing assistant",
  "description": "Shows live pricing and captures budget during sales calls.",

  "skill": {                                  // → a meeting skill
    "trigger": "pricing_assistant: when the participant asks about price, plans, or budget.",
    "instructions": "Confirm which plan they are considering. Call show_pricing once, then …",
    "gated_tools": ["show_pricing"]           // tools below that stay locked until the skill is invoked
  },

  "tools": [                                  // → custom functions
    {
      "name": "show_pricing",
      "description": "Show the pricing sheet for a plan.",
      "parameters": { "type": "object", "properties": { "plan": { "type": "string" } }, "required": ["plan"] },
      "execution": { "mode": "webhook", "url": "https://skills.acme.com/waterr/show_pricing", "timeout_ms": 8000 },
      "on_call": "generate_filler", "on_resolve": "add_to_context"
    }
  ],

  "renderers": [                              // → screen apps the agent (or your server) can present
    { "name": "pricing_sheet", "url": "https://skills.acme.com/waterr/pricing-sheet",
      "description": "Interactive pricing table; the participant can pick a plan.",
      "placement": "side", "allow_participant_events": true }
  ],

  "events": {                                 // → a webhook endpoint owned by the install
    "url": "https://skills.acme.com/waterr/events",
    "subscribe": ["meeting.started", "screen_share.started", "stage.interaction", "meeting.ended"]
  },

  "scopes": ["actions:context", "actions:stage", "events:read"],

  "config_schema": {                          // what the installer fills in
    "type": "object",
    "properties": { "price_book_id": { "type": "string", "title": "Price book" } },
    "required": ["price_book_id"]
  },
  "secrets": ["ACME_API_KEY"]                 // names only; values are entered per install, never returned
}
```

Every limit is the one the hand-editing API enforces: trigger ≤ 500 chars,
instructions ≤ 8,000, ≤ 20 tools, ≤ 10 renderers, https URLs only, 64 KB total.
Validation errors come back as a list in `errors`.

## What an install creates

| Manifest section | Becomes | Notes |
| - | - | - |
| `skill` | a skill named after the package (`acme_pricing_assistant`) | same row shape as `POST /v1/skills` |
| `tools[]` | one custom function each, same names | a name already used by a hand-made tool fails the install with `409 tool_name_conflict` |
| `renderers[]` | screen apps presented as `<package>__<renderer>` (`acme_pricing_assistant__pricing_sheet`) | merged into the scenario's screen apps at meeting time; never written into the scenario's own list, and not counted against its 10-app cap |
| `events` | one webhook endpoint | its signing secret is returned **once** in the install response as `events_signing_secret` |

Webhook-mode tools get a signing secret each, returned once as
`tool_signing_secrets`. Rows created by an install carry `managed_by_install_id`
and the normal edit/delete endpoints answer `409 managed_resource` for them —
change them by upgrading the package.

## Install response

```json theme={null}
{
  "data": {
    "id": "inst_…",
    "package_id": "acme.pricing_assistant",
    "version": "1.2.0",
    "status": "active",
    "config": { "price_book_id": "pb_2026" },
    "secret_names": ["ACME_API_KEY"],
    "materialised": {
      "skill": { "id": "…", "name": "acme_pricing_assistant", "active": true },
      "tools": [{ "id": "…", "name": "show_pricing", "execution_mode": "webhook" }],
      "renderers": [{ "name": "pricing_sheet", "presented_as": "acme_pricing_assistant__pricing_sheet", "placement": "side" }],
      "events_endpoint": { "id": "…", "enabled": true, "subscribed_events": ["meeting.started", "stage.interaction"] },
      "scopes": ["actions:context", "actions:stage", "events:read"]
    },
    "events_signing_secret": "whsec_…",
    "tool_signing_secrets": { "show_pricing": "whsec_…" }
  }
}
```

## Lifecycle

| Call | Effect |
| - | - |
| `PATCH /v1/skill-installs/{id}` `{ config, secrets, status }` | Update settings; `status: "disabled"` switches the skill, its tools and its endpoint off without removing anything |
| `POST /v1/skill-installs/{id}/upgrade` `{ version }` | Move to another registered version. The skill keeps its id (attachments survive); tools are reconciled by name; the endpoint is updated. Fails with `400 invalid_config` if the current config no longer satisfies the new `config_schema` |
| `DELETE /v1/skill-installs/{id}` | Uninstall: every row the install created is removed, attachments with them |
| `POST /v1/skill-packages` with the same `id@version` | Idempotent when the contents match; `409 version_exists` when they differ — bump the version |

## Acting as the install

`POST /v1/skill-installs/{id}/keys` mints a **`wsk_live_…`** key, shown once. It
is accepted by the [Meeting Actions API](https://docs.waterr.ai/api-reference/meeting-actions.md) as a
bearer and acts *as the install*:

* only on meetings whose scenario has the install attached (`404` otherwise);
* only the action types the manifest's `scopes` grant (`actions:context`,
  `actions:message`, `actions:say`, `actions:stage`) — `403 scope_denied` otherwise;
* `stage.*` only on the install's own renderers.

Actions it pushes are recorded with `actor: "skill:<package_id>"`. Revoke a key
with `DELETE /v1/skill-installs/{id}/keys/{key_id}`.

## Not yet

* A catalog: packages are registered per workspace; sharing one between
  workspaces means registering it in each.
* Delivering `config` and `secrets` to your tool-call webhooks automatically —
  read them back from `GET /v1/skill-installs/{id}` for now (secrets are never
  returned; `secret_names` tells you which are set).
* Sharing config and secrets with your own tool webhooks — for now your
  backend reads config from the install and holds its own secrets.

In Studio, installs live under **Developer → Skills → Installed packages**:
install from a URL or pasted manifest, flip which scenarios it runs on, mint
keys, upgrade, pause and uninstall.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.