# Manifest (https://docs.hub.ki/custom-apps/manifest)



Every app folder contains a `manifest.json`. The platform validates it when it
syncs a source; a manifest that fails validation is skipped with an error.

```json
{
  "appId": "ai.justadd.stickyboard",
  "slug": "stickyboard",
  "name": { "en": "Stickyboard", "de": "Stickyboard" },
  "version": "2.3.0",
  "sdkVersion": "1.0.0",
  "artifactPath": "app.html",
  "iconPath": "icon.svg",
  "events": ["board-updated"],
  "scopes": ["workspace:own"],
  "connectOrigins": ["https://api.example.com"]
}
```

## Fields
| Field            | Type                     | Required | Description                                                                                      |
| ---------------- | ------------------------ | -------- | ------------------------------------------------------------------------------------------------ |
| `appId`          | `string`                 | yes      | Globally unique identifier. Reverse-DNS is recommended (`com.example.app`).                      |
| `slug`           | `string`                 | yes      | Short, URL-safe name used in the app's route.                                                    |
| `name`           | `Record<locale, string>` | yes      | Localized display names. Include at least `en`; it is the fallback.                              |
| `version`        | `string`                 | yes      | App version, numeric and dotted (`1.4.0`, optionally `1.4.0-beta.1`). Bump it to ship an update. |
| `sdkVersion`     | `string`                 | yes      | Bridge contract you target, as exact `major.minor.patch`. The host supports major `1`.           |
| `artifactPath`   | `string`                 | yes      | Path to the single HTML file, relative to the folder.                                            |
| `icon`           | `string`                 | \*       | A [Lucide](https://lucide.dev/icons) icon name, e.g. `sticky-note`.                              |
| `iconPath`       | `string`                 | \*       | Path to an icon image instead (`svg`, `png`, `jpg`, `webp`, `gif`; ≤ 256 KB).                    |
| `events`         | `string[]`               | no       | Event names the app may [publish or receive](https://docs.hub.ki/custom-apps/bridge#events).                               |
| `description`    | `{ [locale]: string }`   | no       | Short description shown on the marketplace card, keyed by locale like `name`.                    |
| `scopes`         | `string[]`               | no       | Capabilities the app requests (see below).                                                       |
| `connectOrigins` | `string[]`               | no       | Backend origins the app may call from the browser (see [Network access](#network-access)).       |

\* Provide **either** `icon` or `iconPath`.

## Scopes
Scopes are the only capabilities an app has beyond rendering itself. Request the
minimum you need.

| Scope             | Grants                                                                                               | Approval                          |
| ----------------- | ---------------------------------------------------------------------------------------------------- | --------------------------------- |
| `workspace:own`   | The app's own private, per-user workspace (documents and files).                                     | None — always available.          |
| `store:own`       | A per-user, per-version key/value store for the app.                                                 | None — always available.          |
| `documents:read`  | Read the user's shared organization workspace documents.                                             | Org admin must approve the scope. |
| `user:read`       | Read the signed-in user's name and email.                                                            | Org admin must approve the scope. |
| `api:access`      | Get an access token to call HUB.KI APIs from the app's own backend, as the user.                     | Org admin must approve the scope. |
| `responses:run`   | Run the HUB.KI AI (Responses API) at the organization's expense via an automatically issued API key. | Org admin must approve the scope. |
| `capability:wasm` | Run WebAssembly in the app sandbox (`wasm-unsafe-eval`).                                             | Org admin must approve the scope. |

`workspace:own` and `store:own` are granted automatically. Any other scope must
be approved by an organization admin from **Manage availability** before the app
can use it — an un-approved call is rejected.

Web Workers are available to every app (`worker-src blob:`), so heavy work can
leave the main thread without asking for anything. WebAssembly is different: it
widens what may execute in the sandbox, so it is opt-in through
`capability:wasm` and, like any other approval, the org admin sees it before
turning it on. Libraries such as pdf.js that can run either way will fall back to
their non-WASM path when it is not granted.

## Validation
A sync validates every manifest and its files. An app that fails any rule is
skipped as a whole — and if it was published before, it goes offline (see
[Syncing](https://docs.hub.ki/custom-apps/publishing#syncing)). The admin interface only
reports the number of skipped apps, so check these before uploading:

| Rule                                                                                             | Typical mistake                                      |
| ------------------------------------------------------------------------------------------------ | ---------------------------------------------------- |
| The file is valid JSON and every required field is present.                                      | Trailing comma; missing `name` or `artifactPath`.    |
| `appId`: letters, digits, `.`, `-`, `_`; segments never empty; at most 255 characters.           | Spaces, a leading or trailing dot.                   |
| `slug`: lowercase letters and digits separated by single hyphens; at most 128 characters.        | Uppercase letters, underscores, spaces.              |
| `sdkVersion`: exactly `major.minor.patch` with a supported major (`1`).                          | `"1"`, `"1.0"`, `"^1.0.0"`, `"2.0.0"`.               |
| `icon` **or** `iconPath` is set. An icon file is `svg`, `png`, `jpg`, `webp` or `gif`, ≤ 256 KB. | Neither given; icon too large.                       |
| Every entry in `scopes` is a known scope (table above); at most 20.                              | A typo such as `storage:own`.                        |
| Every entry in `connectOrigins` is an exact `https://` origin (see below); at most 8 are used.   | A path, a wildcard, plain `http://`.                 |
| `artifactPath` points to an existing file of at most 10 MB.                                      | Manifest says `app.html`, folder holds `index.html`. |
| The artifact starts like an HTML document (`<!doctype html` or `<html` near the top).            | Uploading a JavaScript bundle instead of the page.   |
| `appId` + `version` appears in only one folder of the source.                                    | Copying a folder without bumping `version`.          |
| `appId` is not already published by another source.                                              | Reusing an example `appId`.                          |

Further limits: `name` values up to 200 characters, `description` values up to
2,000, `version` up to 32, and up to 100 `events` of up to 100 characters each.

## Network access
Apps run in a sandboxed iframe whose Content-Security-Policy blocks **all**
network requests by default — `fetch`, `XMLHttpRequest`, `WebSocket`,
`EventSource`, and `sendBeacon` to any origin, including your own backend. To let
your app talk to a server, list its exact origins in `connectOrigins`:

```json
"connectOrigins": ["https://api.example.com", "https://events.example.com"]
```

Rules:

* Each entry must be an **exact origin** — scheme + host + optional port, no path,
  no wildcards. `https://` is required; `http://` is allowed only for
  `localhost` / `127.0.0.1` during development. Up to 8 origins.
* Declaring an origin is a **request**, not a grant. An organization admin
  approves each origin in **Manage availability**; only approved origins are
  added to the app's `connect-src`. Unapproved origins stay blocked.

See [Calling HUB.KI from your backend](https://docs.hub.ki/custom-apps/backend) for the
end-to-end flow and the CORS requirement.

## Versioning
* **`version`** identifies your release. Publish a new `version` to roll out
  changes; the platform re-reads the artifact on its next sync.
* **`sdkVersion`** declares the bridge contract. If its major version is not
  supported, the host refuses to run the app and shows an upgrade notice.
