# Applications CLI commands

> Canonical HTML: https://screenrig.ai/docs/cli/applications

Pack, upload, update and delete web applications, and read or write their key/value store and its webhooks. Every command here runs as described in the [CLI reference](https://screenrig.ai/docs/cli.md): global flags, JSON output, exit codes, revisions and retries.

## Applications

An application is one already-built static directory with a root `index.html`. The packer injects the browser SDK at `_screenrig/runtime.js` and adds its script tag, so the app reaches `window.screenrig` with no build step and nothing to add to its dependencies. Nothing in the directory is ever built or executed during packing.

### `app pack`

Pack a static directory into a deterministic archive without uploading it.

```console
app pack <directory> [--output FILE]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<directory>` | path | yes | none | The built static directory. It needs a root `index.html`. |
| `--output FILE` | path | no | nothing is written | Also write the archive to this path. Without it the command reports the archive and keeps it in memory. |

```console
screenrig app pack ./lobby-board --output ./lobby-board.tgz
```

The envelope reports `sha256`, `compressed_bytes`, `expanded_bytes`, `file_count`, the entry list, and whether the SDK runtime was injected. `app upload` packs the directory itself, so reach for this command only when you want to inspect the archive first. It runs locally and needs no sign-in.

A package holds at most 5000 files, 250 MiB expanded and 100 MiB compressed, each file at most 32 MiB, with paths of at most 16 levels and 255 bytes. Packing leaves out names that start with `.`, `node_modules/`, `bower_components/`, `jspm_packages/`, `vendor/`, `venv/`, `__pycache__/`, `CVS/`, `*.map` source maps, key and credential files (`*.pem`, `*.key`, `*.p12`, `*.pfx`, `*.crt`, `*.cer`, `id_rsa`, `id_dsa`, `id_ecdsa`, `id_ed25519`, `credentials.json`, `secrets.json`), editor swap and backup files, `Thumbs.db` and `desktop.ini`. A `.screenrigignore` file at the directory root adds gitignore-style patterns, one per line, and cannot bring back a built-in exclusion. Symlinks, hardlinks, sparse files, devices, duplicate paths (compared case-insensitively too), files that change while packing and a directory without a root `index.html` are refused with the path named: the command exits 2 and sends nothing.

Applications run under a content security policy that blocks inline code. Packing moves inline style and script blocks into `_screenrig-inline-*` files beside each HTML document and links them in. Inline event handlers such as `onclick=` and `javascript:` URLs cannot be moved; packing refuses them and names the file and line.

### `app types`

Write the TypeScript types for `window.screenrig` into a directory.

```console
app types [directory]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `[directory]` | path | no | the current directory | The directory to write `screenrig.d.ts` into. It must exist. |

```console
screenrig app types ./src
```

The envelope reports `path`, `bytes`, `sha256` and `written`, which is `false` when the file already held the current types. The file matches the runtime that `app pack` and `app upload` inject, so run the command again after you update the CLI. An existing `screenrig.d.ts` is replaced whole; a directory or a symlink at that path is refused, and the command exits 2. It runs locally and needs no sign-in. [TypeScript](https://screenrig.ai/docs/sdk.md#typescript) covers what the types declare.

### `app dev`

Serve a directory in a local Player harness to try an application before you upload it.

```console
app dev <directory> [--port PORT] [--open] [--no-reload] [--kv FILE | --kv-from APP_ID]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<directory>` | path | yes | none | The built static directory. It needs a root `index.html`. |
| `--port PORT` | integer | no | 18890 | The port the harness listens on. The runtime trusts the harness on 18890 alone, so any other port is refused. |
| `--open` | boolean | no | off | Open the harness in your browser. |
| `--no-reload` | boolean | no | off | Keep the page as it is when a file changes. Without it the page reloads on every change. |
| `--kv FILE` | path | no | `.screenrig-dev/kv.json` | The JSON file that holds the key/value store of the run. |
| `--kv-from APP_ID` | string | no | none | Read the key/value store of this uploaded application, read-only, instead of a file. |

```console
screenrig app dev ./lobby-board --open
```

The command serves the harness at `http://play.screenrig.localhost:18890` and runs until you stop it. It injects the runtime as `app pack` does, and reports what packing refuses, naming the file and line, as you edit. The harness simulates the capabilities, the viewport, the screen name, time zone and params, the page timer, pause, display and leaving, device events, and serial ports, GPIO lines, Bluetooth LE peripherals and printers. `emit` and `log` lines print in the terminal. [Develop locally](https://screenrig.ai/docs/sdk.md#develop) covers the controls.

### `app upload`

Pack a directory, publish it as a new application release, and wait for the publication to succeed.

```console
app upload <directory> [--name NAME] [--no-wait] [--poll-ms MS] [--timeout MS]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<directory>` | path | yes | none | The built static directory. |
| `--name NAME` | string | no | a generated name | Application name, 1 to 120 characters after trimming, without control characters. |
| `--no-wait` | boolean | no | off | Return before the publication operation finishes. Run `operations wait` before pinning the release into a playlist. |
| `--poll-ms MS` | integer | no | 1000 | Interval between operation polls while waiting. |
| `--timeout MS` | integer | no | 120000 | Deadline for the wait. It also bounds each underlying request. |

```console
screenrig app upload ./lobby-board --name "Lobby board"
```

Read three fields: `data.operation.state` is `succeeded`, `data.application.release_id` is the `rel_` id an `application` primitive pins, and `data.application.id` is the `app_` id the `kv` commands take. A succeeded operation, not a release id on its own, is what means the release is usable.

Every `app upload` creates a new application and a first immutable release. To publish a later release of the same app, use `app update`. A playlist keeps serving the release it already pins until you edit that primitive.

A transfer to storage that is cut off is tried up to 4 times while its upload URL is valid. If it still fails, run the same command again to resume the same upload.

### `app update`

Publish a new release of an existing application, preserving identity, name, and application K/V.

```console
app update <id> <directory> [--expect-rev REVISION] [--no-wait] [--poll-ms MS] [--timeout MS]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<id>` | string | yes | none | The `app_` application id. |
| `<directory>` | path | yes | none | The built static directory. |
| `--expect-rev REVISION` | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is `revision_conflict`: refetch, reapply, retry. |
| `--no-wait` | boolean | no | off | Return before the publication operation finishes. Run `operations wait` before pinning the release into a playlist. |
| `--poll-ms MS` | integer | no | 1000 | Interval between operation polls while waiting. |
| `--timeout MS` | integer | no | 120000 | Deadline for the wait. It also bounds each underlying request. |

```console
screenrig app update app_01EXAMPLEAPP00000000000 ./lobby-board
```

The name stays as it is; change it with `app rename`. Wait for `data.operation.state: "succeeded"`, then use `playlist replace-release` to review the exact page, primitive, new release, and affected screens. Apply with the returned impact token and an optional revision guard as described in the [playlist workflow](https://screenrig.ai/docs/cli.md#playlist-workflows). Existing playlist pins remain unchanged until apply. On a revision conflict, read the app again before deciding whether to retry; do not create a replacement application just to bypass the conflict.

### `app list`

List the project's applications.

```console
app list
```

No parameters.

```console
screenrig app list
```

### `app rename`

Change an application's display name.

```console
app rename <id> --name NAME [--expect-rev REVISION]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<id>` | string | yes | none | The `app_` application id. |
| `--name NAME` | string | yes | none | The new name, 1 to 120 characters after trimming, without control characters. |
| `--expect-rev REVISION` | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is `revision_conflict`: refetch, reapply, retry. |

```console
screenrig app rename app_01EXAMPLEAPP00000000000 --name "Lobby welcome"
```

Only the display name changes. The application id, its releases, its key/value data and the playlists that pin its releases stay as they are, and no directory is uploaded. Renaming to the current name leaves the revision unchanged. Names may repeat; address an application by its id.

### `app show`

Read one application.

```console
app show <id>
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<id>` | string | yes | none | The `app_` application id. |

```console
screenrig app show app_01EXAMPLEAPP00000000000
```

`latest_ready_release` is absent until a first publish reaches ready. Publish state lives on the operation and the release, not on the application. `last_used_at` is absent until a screen first plays the application.

### `app delete`

Delete an application and every one of its releases.

```console
app delete <id> [--expect-rev REVISION]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<id>` | string | yes | none | The `app_` application id. |
| `--expect-rev REVISION` | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is `revision_conflict`: refetch, reapply, retry. |

```console
screenrig app delete app_01EXAMPLEAPP00000000000
```

There is no per-release delete. While a screen shows one of the application's releases, the delete answers `application_in_use`: give those screens other content first. The delete does not check playlists that pin a release without showing it, so update or delete those playlists too.

## Application key/value

Key/value storage belongs to one application and is binary-safe. Every command takes `--app-id` with the `app_` id from `app upload`, never a release id. Use exactly one value mode per write.

### `kv list`

List the keys stored for one application.

```console
kv list --app-id ID
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `--app-id ID` | string | yes | none | The `app_` application id. |

```console
screenrig kv list --app-id app_01EXAMPLEAPP00000000000
```

### `kv get`

Read one key.

```console
kv get --app-id ID <key>
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `--app-id ID` | string | yes | none | The `app_` application id. |
| `<key>` | string | yes | none | The key to read. |

```console
screenrig kv get --app-id app_01EXAMPLEAPP00000000000 menu
```

The answer carries the value as `data.value_base64`, JSON values included: decode it, and parse JSON only when `data.content_type` says JSON. `kv list` omits values.

### `kv set`

Write one key from JSON, a file, or base64 bytes.

```console
kv set --app-id ID <key> --json-value JSON [--expect-rev REVISION]
kv set --app-id ID <key> --file FILE [--content-type TYPE] [--expect-rev REVISION]
kv set --app-id ID <key> --value-base64 BASE64 [--content-type TYPE] [--expect-rev REVISION]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `--app-id ID` | string | yes | none | The `app_` application id. |
| `<key>` | string | yes | none | The key to write. |
| `--json-value JSON` | string | one mode | none | A JSON value, canonicalized before it is sent, always stored as `application/json`. An explicit `application/json` content type is also accepted. |
| `--file FILE` | path | one mode | none | Read the value bytes from a file, at most 1 MiB, or from stdin with `-`. Defaults to `application/octet-stream`. |
| `--value-base64 BASE64` | string | one mode | none | Canonical padded standard base64 without whitespace, at most 1,398,104 characters; an empty value is valid. Defaults to `application/octet-stream`. |
| `--content-type TYPE` | string | no | application/octet-stream for bytes | One to 127 characters. |
| `--expect-rev REVISION` | string | no | none | Write only if the stored entry is still at this revision. |

```console
screenrig kv set --app-id app_01EXAMPLEAPP00000000000 menu --json-value '{"soup":"lentil"}'
```

### `kv delete`

Delete one key.

```console
kv delete --app-id ID <key> [--expect-rev REVISION]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `--app-id ID` | string | yes | none | The `app_` application id. |
| `<key>` | string | yes | none | The key to delete. |
| `--expect-rev REVISION` | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is `revision_conflict`: refetch, reapply, retry. |

```console
screenrig kv delete --app-id app_01EXAMPLEAPP00000000000 menu
```

## Key/value webhooks

A key/value webhook is a URL that writes one key of one application, so any outside system, such as a point of sale, a spreadsheet script or a cron job, pushes data to screens with one HTTP POST. An application that subscribes with [`kv.onChange`](https://screenrig.ai/docs/sdk#kv-onchange) updates within seconds on every screen that runs it. An application has at most 20 webhooks.

The sender authenticates with the webhook's secret, `srkw_…`, as `Authorization: Bearer <secret>`, or with HTTP Basic using the webhook id as the username and the secret as the password; a secret in the URL or query string is never accepted. The raw body, at most 1 MiB, becomes the key's value and `Content-Type` its content type. Each post does not count toward the agent request cap, is counted toward storage like `kv set`, raises the revision, and answers with the key's new metadata, never its value. An optional `Idempotency-Key` header deduplicates a sender's retries for 24 hours. One webhook takes at most 120 posts a minute, and a missing or wrong secret or an unknown webhook answers `401`. [Push data from another system](https://screenrig.ai/docs/sdk#kv-webhooks) has `curl` examples.

The secret is shown only by `kv webhook create` and `kv webhook rotate-secret`. The URL is not secret. Creating, rotating and deleting a webhook append `kv_webhook.created`, `kv_webhook.secret_rotated` and `kv_webhook.deleted` to the project event log, and each post appends `kv.updated` like any write.

| Problem code | Exit | Meaning |
| --- | --- | --- |
| `kv_webhook_limit_reached` | `5` | The application or card already has 20 webhooks. Nothing was created. Delete one first. |
| `not_found` | `4` | The application is not in this project, or no webhook has this id on it. |
| `card_not_found` | `4` | The card is not in this project. |

### `kv webhook create`

Create a webhook that writes one key of an application, or with `--card` the data document of a card, and receive its URL and secret once.

```console
kv webhook create <key> --app-id ID [--description TEXT]
kv webhook create --card CARD_ID [--description TEXT] [--map FILE|-] [--key-template TEMPLATE] [--signing]
                  [--verify VENDOR [--verify-secret-file FILE|-] [--verify-header NAME] [--verify-encoding hex|base64]
                   [--verify-timestamp-header NAME] [--verify-tolerance SECONDS]] [--handshake VENDOR]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<key>` | string | with `--app-id` | none | The key every post writes, 1 to 256 characters: a letter or digit first, then letters, digits, `.`, `_`, `:`, `/` or `-`. The key need not exist yet. A card webhook takes no key. |
| `--app-id ID` | string | one of | none | The `app_` application id. Give exactly one of `--app-id` and `--card`. |
| `--card CARD_ID` | string | one of | none | The `crd_` card id: the webhook writes the card's data document. See [Card webhooks](https://screenrig.ai/docs/cli/cards.md#card-webhooks). |
| `--description TEXT` | string | no | none | What sends to it, at most 200 bytes. It must not hold a credential. |
| `--map FILE` | path | no | none | The mapping that builds the document from the posted body: a JSON object of at most 64 KiB read from a file, or `-` for stdin. Without it the posted object is the document. |
| `--key-template TEMPLATE` | string | no | none | Write keyed documents under a key filled from `{screen.field.NAME}` tokens, such as `room-{screen.field.room}`. |
| `--signing` | boolean | no | off | Also require `ScreenRig-Signature` on every post and print the signing secret once. Not with `--verify`. |
| `--verify VENDOR` | string | no | none | Accept posts that carry this vendor's own signature in place of the webhook's `srkw_` secret, which keeps working beside it: `github`, `stripe`, `shopify`, `slack`, `linear`, `vercel`, `asana` or `generic_hmac`. |
| `--verify-secret-file FILE` | path | with `--verify` | none | Read the vendor's secret from a file, or `-` for stdin (never an argument): the secret GitHub, Stripe, Shopify, Slack, Linear or Vercel shows, or a `generic_hmac` sender's. `asana` takes none: its handshake delivers it. Only one option reads stdin. |
| `--verify-header NAME` | string | with `generic_hmac` | none | The header that carries the signature, 1 to 100 letters, digits, `-` or `_`. |
| `--verify-encoding ENCODING` | string | no | `hex` | With `generic_hmac`: how the signature is written, `hex` or `base64`; it may begin with `sha256=`. |
| `--verify-timestamp-header NAME` | string | no | none | With `generic_hmac`: a header that carries the Unix time in seconds, or in milliseconds as 13 digits. The signed text is then the timestamp, a dot and the body. |
| `--verify-tolerance SECONDS` | integer | no | `300` | With `--verify-timestamp-header`: how far the timestamp may sit from the server's clock, 1 to 86400. |
| `--handshake VENDOR` | string | no | none | Answer the setup challenge of `microsoft_graph`, `monday`, `trello` or `azure_event_grid`. Slack and Asana answer theirs through `--verify`. |

```console
screenrig kv webhook create menu --app-id app_01EXAMPLEAPP00000000000 --description "Point of sale menu feed"
screenrig kv webhook create --card crd_01EXAMPLEBOARD0000000000 --map alert-map.json --description "CMS alerts"
```

`--map`, `--key-template`, `--signing`, `--verify` with its options and `--handshake` apply to a card webhook only.

The envelope is the webhook: `data.id` (`kvw_…`), `data.key` or, for a card, `data.card_id`, `data.url` (`https://api.screenrig.ai/api/applications/<app_id>/kv-webhooks/<id>/write`, or `https://api.screenrig.ai/api/cards/<card_id>/kv-webhooks/<id>/write`) and `data.secret` (`srkw_…`). A signing card webhook also carries `data.signing_secret` (`whsec_…`). Enter the URL and secrets in the sending system now. No later command shows them again. After an interrupted or ambiguous create, rerun the identical command within 24 hours: it reuses the saved idempotency key, and the server returns the same webhook and secrets instead of creating another.

### `kv webhook list`

List the webhooks of an application or a card, oldest first.

```console
kv webhook list (--app-id ID | --card CARD_ID)
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `--app-id ID` | string | one of | none | The `app_` application id. Give exactly one of `--app-id` and `--card`. |
| `--card CARD_ID` | string | one of | none | The `crd_` card id. |

```console
screenrig kv webhook list --app-id app_01EXAMPLEAPP00000000000
screenrig kv webhook list --card crd_01EXAMPLEBOARD0000000000
```

Each item has `id`, `key`, `description`, `url`, `created_at` and, once it has written, `last_used_at`. A card webhook's item has `card_id` in place of `key`, plus `mapping` (`null` when the posted object is the document), `enabled`, `signing` and, when set, `key_template`, `verify` and `handshake`. A list never carries a secret.

### `kv webhook delete`

Delete a webhook. Its URL stops working at once, and the key or the card's document keeps its value.

```console
kv webhook delete <webhook_id> (--app-id ID | --card CARD_ID)
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<webhook_id>` | string | yes | none | The `kvw_` webhook id. |
| `--app-id ID` | string | one of | none | The `app_` application id. Give exactly one of `--app-id` and `--card`. |
| `--card CARD_ID` | string | one of | none | The `crd_` card id. |

```console
screenrig kv webhook delete kvw_01EXAMPLEWEBHOOK00000 --app-id app_01EXAMPLEAPP00000000000
screenrig kv webhook delete kvw_01EXAMPLEWEBHOOK00000 --card crd_01EXAMPLEBOARD0000000000
```

### `kv webhook rotate-secret`

Replace a webhook's secret and receive the new one once.

```console
kv webhook rotate-secret <webhook_id> (--app-id ID | --card CARD_ID) [--signing]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<webhook_id>` | string | yes | none | The `kvw_` webhook id. |
| `--app-id ID` | string | one of | none | The `app_` application id. Give exactly one of `--app-id` and `--card`. |
| `--card CARD_ID` | string | one of | none | The `crd_` card id. |
| `--signing` | boolean | no | off | With `--card`: replace the signing secret instead of the webhook secret. |

```console
screenrig kv webhook rotate-secret kvw_01EXAMPLEWEBHOOK00000 --app-id app_01EXAMPLEAPP00000000000
screenrig kv webhook rotate-secret kvw_01EXAMPLEWEBHOOK00000 --card crd_01EXAMPLEBOARD0000000000 --signing
```

The envelope carries the new secret in `data.secret`. The old secret stops working at once, so enter the new one in the sending system. With `--signing` the envelope carries the new signing secret in `data.signing_secret` instead, and the webhook's `srkw_` secret stays. After an interrupted or ambiguous rotation, rerun the identical command: it reuses the saved idempotency key, and the server returns the rotation that already happened instead of rotating again.

## Other command groups

- [Projects](https://screenrig.ai/docs/cli/projects.md)
- [Service clients](https://screenrig.ai/docs/cli/service-clients.md)
- [Help and support](https://screenrig.ai/docs/cli/help.md)
- [Media](https://screenrig.ai/docs/cli/media.md)
- [Media sync](https://screenrig.ai/docs/cli/media-sync.md)
- [Playlists](https://screenrig.ai/docs/cli/playlists.md)
- [Cards](https://screenrig.ai/docs/cli/cards.md)
- [Screens](https://screenrig.ai/docs/cli/screens.md)
- [Schedules](https://screenrig.ai/docs/cli/schedules.md)
- [Campaigns](https://screenrig.ai/docs/cli/campaigns.md)
- [Device health](https://screenrig.ai/docs/cli/device.md)
- [Reviews and notes](https://screenrig.ai/docs/cli/reviews.md)
- [Notifications](https://screenrig.ai/docs/cli/notifications.md)
- [Reports](https://screenrig.ai/docs/cli/reports.md)
- [Data sources](https://screenrig.ai/docs/cli/data-sources.md)
- [BI dashboards](https://screenrig.ai/docs/cli/bi.md)
- [Triggers](https://screenrig.ai/docs/cli/triggers.md)
- [Fleet links](https://screenrig.ai/docs/cli/fleet-links.md)
- [Advertising](https://screenrig.ai/docs/cli/ads.md)
