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

# Manifest reference

> Every field of voiceos.integration.json, with the validation rules that apply.

The manifest is the root of an integration: one JSON file,
`voiceos.integration.json`, validated on install and on every reload.
Validation errors are flat `path: message` strings; anything not listed here
is rejected or ignored.

## The smallest valid manifest

Everything else on this page is optional or a variation of these eight fields:

```json theme={null}
{
  "schemaVersion": 1,
  "id": "com.you.coffee-tracker",
  "version": "1.0.0",
  "name": "Coffee Tracker",
  "summary": "Log every coffee by voice and glance your habits in the notch.",
  "publisher": { "id": "pub_you", "name": "You" },
  "runtime": { "kind": "local-mcp", "command": "bun", "args": ["server.ts"] },
  "tools": [
    {
      "name": "coffee_stats",
      "description": "Show how much coffee the user has been drinking. Use when the user asks about their coffee habits, count, or stats.",
      "inputSchema": { "type": "object", "properties": {} }
    }
  ]
}
```

## Top level

| Field           | Type                 | Required | Rules                                                                                                                                     |
| --------------- | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `schemaVersion` | `1`                  | ✅        | Literal `1`.                                                                                                                              |
| `id`            | `string`             | ✅        | Reverse-DNS: `/^[a-z0-9]+(\.[a-z0-9-]+)+$/` (e.g. `com.you.coffee-tracker`). **Immutable** — settings, secrets, and installs hang off it. |
| `version`       | `string`             | ✅        | Semver, `major.minor.patch`. Versions your integration, not the schema.                                                                   |
| `name`          | `string`             | ✅        | Non-empty display name.                                                                                                                   |
| `summary`       | `string`             | ✅        | 1–140 chars, one line.                                                                                                                    |
| `description`   | `string`             | —        | Markdown for the detail page.                                                                                                             |
| `icon`          | `string`             | —        | Folder-relative path (`icon.png`, `icon.svg`).                                                                                            |
| `homepage`      | `string`             | —        | Must be a valid URL.                                                                                                                      |
| `repository`    | `string`             | —        | Must be a valid URL. Shown as a trust signal; never cloned or executed.                                                                   |
| `categories`    | `string[]`           | —        | Free-form discovery tags.                                                                                                                 |
| `publisher`     | `Publisher`          | ✅        | See below.                                                                                                                                |
| `runtime`       | `IntegrationRuntime` | ✅        | See below.                                                                                                                                |
| `auth`          | `IntegrationAuth`    | —        | Defaults to `{ "kind": "none" }`.                                                                                                         |
| `permissions`   | `Permission[]`       | —        | See below.                                                                                                                                |
| `preferences`   | `Preference[]`       | —        | See below.                                                                                                                                |
| `tools`         | `ToolDefinition[]`   | ✅        | **At least one tool.**                                                                                                                    |

## `publisher`

| Field      | Type      | Required | Notes                                  |
| ---------- | --------- | -------- | -------------------------------------- |
| `id`       | `string`  | ✅        | Stable publisher identity — immutable. |
| `name`     | `string`  | ✅        | Display name.                          |
| `url`      | `string`  | —        | Must be a valid URL.                   |
| `verified` | `boolean` | —        | Set by VoiceOS, never by the author.   |

## `runtime`

One of three shapes:

```json theme={null}
{ "kind": "local-mcp", "command": "bun", "args": ["server.ts"] }
{ "kind": "remote-mcp", "url": "https://mcp.example.com" }
{ "kind": "hosted", "entry": "server.ts" }
```

| Kind         | Meaning                                                                                                                     |
| ------------ | --------------------------------------------------------------------------------------------------------------------------- |
| `local-mcp`  | VoiceOS runs the folder's command on the user's machine over stdio. The default; zero infra.                                |
| `remote-mcp` | VoiceOS connects to your Streamable-HTTP MCP server and brokers auth.                                                       |
| `hosted`     | **Reserved** — a VoiceOS-operated sandbox, coming with the App Store backend. Declared now so manifests won't change later. |

## `auth`

```json theme={null}
{ "kind": "none" }

{ "kind": "apiKey", "fields": [
  { "key": "API_KEY", "label": "API key", "secret": true,
    "placeholder": "sk-…", "required": true }
] }

{ "kind": "oauth2", "provider": "notion",
  "authorizationUrl": "…", "tokenUrl": "…",
  "scopes": ["read", "write"], "pkce": true }
```

* `apiKey` fields default to **`required: true`**; values are collected via
  [setup fields](/integrations/setup-fields), stored encrypted, and injected
  as `process.env.<key>`. Never persisted in plaintext, never shown to the
  agent.
* `oauth2` is in the contract today; the VoiceOS-brokered login flow ships
  with the managed broker. `pkce` defaults to `true`.

## `permissions`

All opt-in, all shown to the user at install time:

| Permission   | Shape                                                   | Grants                                                                                |
| ------------ | ------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `network`    | `{ "kind": "network", "domains": ["api.example.com"] }` | Egress to the listed domains only. Least privilege — list exactly what you call.      |
| `background` | `{ "kind": "background" }`                              | Tools with `execution.mode: "background"`. Required for them — see cross-field rules. |
| `notify`     | `{ "kind": "notify" }`                                  | Completion notifications + opening the side notch.                                    |
| `store`      | `{ "kind": "store", "quotaKb": 128 }`                   | **Reserved** — per-user encrypted key-value store (no SDK API yet).                   |
| `webhook`    | `{ "kind": "webhook" }`                                 | **Reserved** — inbound webhook URL.                                                   |

## `preferences`

| Field         | Type                                                        | Required    | Notes                                                                                                            |
| ------------- | ----------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------- |
| `name`        | `string`                                                    | ✅           | `/^[a-zA-Z][a-zA-Z0-9_]*$/`. Convention: `SCREAMING_SNAKE_CASE` — the value is injected as `process.env.<name>`. |
| `title`       | `string`                                                    | ✅           | Label on the setup form.                                                                                         |
| `description` | `string`                                                    | —           | User-facing help — say *where to get* the value.                                                                 |
| `type`        | `"text" \| "password" \| "boolean" \| "select" \| "number"` | ✅           | `select` renders a picker, `password` a secure field, `boolean` a toggle.                                        |
| `required`    | `boolean`                                                   | —           | Preferences default to **optional**.                                                                             |
| `default`     | `string \| number \| boolean`                               | —           | A field with a default is never "missing".                                                                       |
| `placeholder` | `string`                                                    | —           | —                                                                                                                |
| `options`     | `{ label, value }[]`                                        | select only | Required and non-empty when `type` is `"select"`.                                                                |

## `tools[]`

| Field          | Type                             | Required | Notes                                                                                                                                                                |
| -------------- | -------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`         | `string`                         | ✅        | `snake_case` starting with a letter, unique within the integration. VoiceOS namespaces it as `<integrationId>.<name>`.                                               |
| `title`        | `string`                         | —        | Short human title for UI.                                                                                                                                            |
| `description`  | `string`                         | ✅        | **The agent's routing rule** — what it does + when to use it.                                                                                                        |
| `inputSchema`  | JSON Schema object               | ✅        | Draft 2020-12. Describe every property.                                                                                                                              |
| `execution`    | `{ mode, estimatedDurationMs? }` | —        | `mode`: `"sync"` (default; return within the turn) or `"background"` (returns a handle; tracked in the side notch, glance card on completion).                       |
| `confirmation` | `UiView`                         | —        | Rendered **before** the handler runs. Required in spirit for anything that acts; must be omitted for pure reads. See [UI blocks](/integrations/reference/ui-blocks). |

## Cross-field validation

These fire even when every field parses:

1. A tool with `execution.mode: "background"` requires the `background`
   permission.
2. A `select` preference must declare non-empty `options`.
3. An `auth` field and a preference **cannot share a name** — they share one
   config namespace and one env var.
4. Every `{{binding}}` in a tool's confirmation must name a property of that
   tool's `inputSchema`.
5. A confirmation view may contain **at most one `widget`** block, and a
   widget view may not also contain `button`/`actions` blocks.
6. `canvas` blocks are rejected in `schemaVersion: 1` (reserved for a future
   sandboxed renderer), and would additionally require a `network` permission.
7. `role: "custom"` buttons/actions are rejected (reserved for a future
   server round-trip).

## Validating

```bash theme={null}
bunx @voiceos/integration-sdk validate voiceos.integration.json
```

or in code: `validateManifest(json)` / `validateView(view)` from
`@voiceos/integration-sdk` — both return
`{ ok: true, value } | { ok: false, errors: string[] }`.
