# Publish with Run

> Create an app in Run, try it on your own computer, publish versions from the terminal, a pipeline or your AI coding assistant, and use komplai from the app through the app SDK.

You need a built browser app, either one HTML file or a folder with `index.html` such as `dist`, and Run turned on for your organisation. The terminal commands need Node.js 22.15 or later. [Run](https://developers.komplai.dk/tools/run) describes who can open an app, the limits and where data is stored.

## Create the app

1. In the hub, open **Run** and choose **New app**.
2. Type the app's name. Run suggests an address from the name; change it if you want another, or empty the field to get a random address.
3. Choose **Create app**. The app opens on its own page, ready for the first version.

A new app is Organisation only: members of your organisation sign in to komplai to open it.

## Build for Run

- Deliver one self-contained HTML file, or a folder or ZIP file with `index.html` at the top. In a ZIP file, everything may also sit inside one folder.
- Build React, Vite or another framework on your own computer and publish the built output, never the source project.
- Use relative URLs, or URLs that start with `/` on the app's own address. Client-side routes without a file extension get the app's HTML; a missing JavaScript or CSS file stays a 404.
- The packer leaves out hidden files, `node_modules` and source maps, and refuses symbolic links and project files such as `package.json`. Keep `.env` files and keys out of the build: a browser app cannot keep a secret.

## Try it on your own computer

`dev` serves your build at `http://localhost:5180` the way Run does, with the same SDK, routes and Content-Security-Policy, and a local stand-in for komplai:

```bash
npx --package=https://hub.komplai.dk/komplai-publish/komplai-publish-0.2.1.tgz komplai-publish dev ./dist
```

- Every use the app declares in `komplai.json` counts as approved. An undeclared use is refused with `capability_not_approved`, as in Run.
- You are `Udvikler`, an administrator. `--as NAME` changes the name and `--member` makes you a member. At `http://localhost:5180/_komplai/dev` each browser signs in as its own person, so a private window can be a second person, and you can end the session to try `session_expired`.
- Saved data is kept in memory until you stop the server.
- Redact finds patterns only. AI is refused with `ai_not_available_in_dev` unless `KOMPLAI_API_KEY` holds a personal API key with `chat:complete`; calls then go to komplai's API in the EU cloud or through the connection the app names, and EU cloud usage counts towards your organisation's monthly AI spending cap.
- Events, errors, counters and every refusal are printed in the terminal.

`--port` chooses another port.

## Create a publishing key

1. On the app's page, under **Publish the first version** (or **Publish a new version**), choose **Terminal**.
2. Choose **Create a publishing key**. The key is shown only once.
3. Copy the command Run shows. It contains the address of the command-line tool and the app's upload address, but no key.

A publishing key can only publish new versions of that one app. It cannot create apps, change an app's name, address or who can open it, open apps that are Organisation only, or use komplai's AI. It expires after 7 days, and it stops working when it is revoked, when the person who created it leaves the organisation or can no longer change the app, when the app is deleted or when Run is switched off for the organisation. An app can have 10 active keys. Revoke a key under **Publishing keys** on the app's page.

> **Keep the key out of code and chats**
>
> Never put the publishing key in code, Git, a command or a chat with an AI coding assistant. Paste it when the terminal asks, or set `KOMPLAI_PUBLISH_TOKEN` privately in your terminal or your tool's environment.

## Publish a version

See what would be published first. This lists every file, checks the manifest as Run does, and uploads nothing:

```bash
npx --package=https://hub.komplai.dk/komplai-publish/komplai-publish-0.2.1.tgz komplai-publish ./dist --dry-run
```

Then run the command from Run in your project folder. Replace `./dist` if your built files are in another folder:

```bash
npx --package=https://hub.komplai.dk/komplai-publish/komplai-publish-0.2.1.tgz komplai-publish ./dist --endpoint https://hub.komplai.dk/api/publish/APP_UUID/releases
```

The command shows the target app, asks you to confirm and asks for the key at a hidden prompt. The new version is live at once at the app's address. Open the address and check the app: an app that is Organisation only asks you to sign in to komplai, because the publishing key gives no access to view the app.

If the connection fails or the outcome is uncertain, the upload may still have gone through. Check in Run which version is live before you try again. The command never retries by itself.

To avoid typing the upload address each time, save it once. `init` writes `.komplai-publish.json` with the upload address and the build folder, never the key:

```bash
npx --package=https://hub.komplai.dk/komplai-publish/komplai-publish-0.2.1.tgz komplai-publish init --endpoint https://hub.komplai.dk/api/publish/APP_UUID/releases
npx --package=https://hub.komplai.dk/komplai-publish/komplai-publish-0.2.1.tgz komplai-publish ./dist
```

To upload the files yourself with **Upload file** on the app's page, pack them into a ZIP file without sending anything:

```bash
npx --package=https://hub.komplai.dk/komplai-publish/komplai-publish-0.2.1.tgz komplai-publish pack ./dist --out app.zip
```

Install the command-line tool only from the package address in the command Run gives you.

## Publish from a pipeline or a script

The upload API takes the file itself as the request body. Send a ZIP file as `application/zip` or a single HTML file as `text/html`, with the publishing key as a Bearer token:

```bash
curl https://hub.komplai.dk/api/publish/APP_UUID/releases \
  -H "Authorization: Bearer $KOMPLAI_PUBLISH_TOKEN" \
  -H "Content-Type: application/zip" \
  --data-binary @app.zip
```

The command-line tool works in a pipeline too. It reads the key from `KOMPLAI_PUBLISH_TOKEN`, and `--yes` skips the confirmation:

```bash
npx --package=https://hub.komplai.dk/komplai-publish/komplai-publish-0.2.1.tgz komplai-publish ./dist --endpoint https://hub.komplai.dk/api/publish/APP_UUID/releases --yes
```

The endpoint is for terminals and build tools. It refuses any request with an `Origin` header, so JavaScript in a browser cannot call it. Because a publishing key expires after 7 days, a pipeline needs a new key from Run after that.

A successful upload answers HTTP 200 with the app's details: `id`, `activeRelease` (the version that is now live), `visibility` and the saved versions in `releases`, each with its id, when it was created, its unpacked size in bytes, its number of files and the SHA-256 checksum of the uploaded file. Ignore fields you do not recognise. If the archive or a capacity check fails, the version that was live stays live.

| Status | What happened |
| --- | --- |
| 400 | The archive or format is invalid, or the manifest cannot be used (`invalid_manifest`) |
| 401 | The key is invalid, expired or revoked |
| 403 | Missing permission, an `Origin` header, or Run is switched off for the organisation |
| 409 | The app is public and the version declares uses that need sign-in (`public_app_capabilities`) |
| 413 | The upload is over the limit |
| 429 | The traffic or concurrency limit is reached |
| 503 | Run is unavailable, or the outcome is uncertain |
| 507 | No room for the version in storage |

The body is JSON with an `error` code and no credential details. After a 503, check in Run which version is live before you send the file again.

## Publish with your AI coding assistant

1. On the app's page, under **Publish a new version**, choose **AI coding assistant** and **Download the guide (ZIP file)**. The same file is at [hub.komplai.dk/komplai-publish/komplai-publish.zip](https://hub.komplai.dk/komplai-publish/komplai-publish.zip).
2. Unzip it and put the `komplai-publish` folder in `.claude/skills` in your home folder for Claude Code, or in `.codex/skills` for Codex. In a project, Claude Code also reads `.claude/skills` and Codex reads `.agents/skills`. Other assistants can read `SKILL.md` and `API.md` in the folder.
3. Create a publishing key and give the assistant the command from Run, never the key.
4. Start a new conversation and ask the assistant to build or publish the app. In Claude Code you can also type `/komplai-publish`.

The assistant tries the app with `dev`, checks it with `--dry-run` and runs the command; you paste the key when the terminal asks. Once you have approved the publication and the target app, it may add `--yes` to skip the confirmation. If the outcome is uncertain, it does not try again: check in Run which version is live first.

## Use komplai from the app

Load the SDK from the app's own address. `/_komplai/run/v1.js` defines `komplai` on the page, and `/_komplai/run/v1.mjs` exports it as a module. The SDK sends every call to the app's own address with the person's app session, and Run checks the session, the person's access and the administrator's approval on every call. Never call the hub or komplai's API from the app, and never put a key, token or password in it.

Declare what the app uses in `komplai.json` next to `index.html`. With Vite, put the file in `public/` so the build copies it into `dist`. A single HTML file can carry the manifest in `<script type="application/komplai+json">` in its `<head>`. The manifest is served with the app, so it must hold nothing secret, and it can be at most 16 KB.

```json
{
  "sdk": 1,
  "uses": {
    "identity": { "fields": ["name"], "reason": "Shows who booked the room." },
    "data": {
      "bookings": {
        "scope": "app",
        "write": "members",
        "index": ["room", "day"],
        "retainDays": 365,
        "reason": "Bookings everyone in the app can see."
      }
    },
    "log": { "events": ["booking_created"], "reason": "Records bookings in the Audit log." }
  }
}
```

```html
<script src="/_komplai/run/v1.js"></script>
<ul id="bookings"></ul>
<script type="module">
  const list = document.getElementById("bookings");
  try {
    const me = await komplai.me();
    const bookings = komplai.data.collection("bookings");
    const booking = await bookings.add({ room: "A", day: "2026-11-02", name: me.name });
    await komplai.log.event("booking_created", { ref: booking.id });
    const { items } = await bookings.list({ where: { room: "A" } });
    for (const doc of items) {
      const item = document.createElement("li");
      item.textContent = `${doc.data.day}: ${doc.data.name}`;
      list.append(item);
    }
  } catch (error) {
    list.textContent = error.message;
  }
</script>
```

| Call | Needs in the manifest | Answers |
| --- | --- | --- |
| `komplai.app()`, `komplai.theme.current`, `komplai.locale` | nothing | The app's facts, `light` or `dark`, and `da` or `en` |
| `komplai.session` | nothing; a public app has no session | `expiresAt()`, `onExpired(fn)`, `renew()` and `signOut()` |
| `komplai.me()` | `identity` | `memberId` and the approved fields |
| `komplai.ai.chat({ messages })` | `ai.chat` | `{ text, model, processing, usage }` |
| `komplai.redact.findings(text)`, `.pseudonymise(text)`, `.restore(text, placeholders)` | `redact.text` | Findings with offsets and type, or text with labels such as `[PERSON-1]` |
| `komplai.data.collection(name)` | `data` | `add`, `get`, `list`, `update`, `remove`, `changes`, `watch` and `exportMine` |
| `komplai.log.event(name, { ref })`, `.error(error)`, `.count(name)` | `log.events`, `log.errors`, `log.counters` | Writes the event, report or count |

In a collection with `scope` set to `person`, each person sees only their own documents. With `app`, everyone who can open the app reads them, and `write` decides whether all members (`members`) or only the app's creator and your organisation's administrators (`managers`) write. With `organisation`, several apps share the collection. A member changes and removes only the documents they created; the app's creator and the administrators can change any. Pass `{ ifVersion: doc.version }` to `update` when it must not overwrite someone else's change.

When a call is refused, the app gets a `KomplaiError` with a `code`, a `status` and a `message` in the person's language. Show the message, and write data from komplai into the page with `textContent`, never `innerHTML`. The codes you meet first:

- `capability_not_approved`: an administrator has not approved this use for the app yet.
- `session_expired`: the person is no longer signed in to the app. `komplai.session.renew()` signs in again and returns to the page.
- `app_not_assigned`: the person has no access to the tool the app uses, such as Chat or Redact.
- `version_conflict`: someone changed the document meanwhile. Fetch it and try again.

`API.md` in the guide lists every call, option, limit and error code.

## Approve what the app uses

After you publish a version that declares uses, an administrator opens the app's page in Run and goes to **What the app may use**. Each use shows the app's reason and **Approve** or **Decline**; an approved use can be revoked later.

- **AI**: the administrator chooses where the app may use AI, komplai's EU cloud within your organisation's monthly AI spending cap or one of your organisation's own connections, and sets the app's own monthly limit. When the limit is reached, the app's AI stops until the next month.
- **Redact**: the app uses patterns and NER on komplai's servers in Germany. The administrator can also allow Redact's language model in komplai's EU cloud.
- **Send data to …**: approve where the app may send data before the rest.

Try every feature in Run after approval: the local stand-in approves everything.

## Manage the app

- **Versions**: **Use this version** makes an earlier version live again.
- **Take the app offline**: nobody can open the app until you publish or choose a version again. **Make the newest version live again** brings it back.
- **Errors in the app**: errors the app sent from the browser in the last 30 days. Only the person who created the app and your organisation's administrators see them.
- **Download the app's data**: all the app's documents, also those each person saved for themselves only. Only your organisation's administrators can download it.
- **Delete app and all versions**: type the app's name to confirm. Its publishing keys stop working, links to it stop working, and its address becomes available to others at once. This cannot be undone.

---

https://developers.komplai.dk/guides/run
