# App instances

Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none.

## GET /v1/app-instances

List app instances

List every app configured in the workspace, including its id, `appKey`, name, configuration, and node. This is the data behind the My apps grid.

Auth: Bearer token. Permission: `app-instance.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Page size. Omit to get every row; pass it to page by `cursor`. |
| `cursor` | query | string | no | The `nextCursor` of the previous page. |
| `count` | query | "1" | no | With `limit`: also return `total`, the number of matching rows. |
| `usableAt` | query | string | no | Location id: only rows usable at that location (homed there, at the workspace root, or shared to it). |

```bash
curl "https://api.brixsignage.com/v1/app-instances" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | array of AppInstance |  |
| `data[].id` | string | App instance id. |
| `data[].spaceId` | string | Workspace id. |
| `data[].appKey` | string | The app type: a key from `GET /v1/apps/catalog` (`clock`, `weather`, `rss`, …). |
| `data[].name` | string |  |
| `data[].config` | any | The app's settings (JSON). The keys depend on `appKey`. |
| `data[].nodeId` | string \| null | Home location; null = workspace root. |
| `data[].lastSnapshotKey` | string \| null | Internal key of the last rendered thumbnail. |
| `data[].lastSnapshotAt` | string \| null |  |
| `data[].importSourceId` | string \| null | Id in the system it was imported from, if imported. |
| `data[].createdAt` | string | ISO-8601 timestamp (UTC). |
| `data[].updatedAt` | string | ISO-8601 timestamp (UTC). |
| `data[].deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. |
| `nextCursor` | string \| null | Present when `?limit` was passed. Send it back as `?cursor=` for the next page; null on the last page. |
| `total` | integer | Total matching rows, when the route computes it. |

```json
{
  "data": [
    {
      "id": "app_3c4d5e6f7a8b9c0d",
      "spaceId": "space_1a2b3c4d5e6f7a8b",
      "appKey": "clock",
      "name": "Lobby clock",
      "config": {
        "format": "24h"
      },
      "nodeId": null,
      "lastSnapshotKey": null,
      "lastSnapshotAt": null,
      "importSourceId": null,
      "createdAt": "2026-09-28T09:00:00.000Z",
      "updatedAt": "2026-09-28T09:00:00.000Z",
      "deletedAt": null
    }
  ]
}
```

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/app-instances

Create an app instance

Configure an app for the workspace with an `appKey`, `name`, and optional `config` and `nodeId`. `appKey` must match one of the apps offered by the store; list the available keys first with GET /v1/apps/catalog.

**Notes.**
- The 201 body is the row as written, not re-read from the database, so columns the create does not set (for example `lastSnapshotAt`) are absent rather than null. `GET` returns every column.

Auth: Bearer token. Permission: `app-instance.create`.

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `appKey` | string | yes | A key from `GET /v1/apps/catalog`. An unknown key is refused (422 `unknown_app`). |
| `name` | string | yes |  |
| `config` | object \| string | no | The app's settings: a JSON object, or the same as a JSON string. Default `{}`. |
| `nodeId` | string \| null | no | Home location. Default: the caller's own location. |

```bash
curl -X POST "https://api.brixsignage.com/v1/app-instances" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"appKey":"clock","name":"Lobby clock","config":{"format":"24h"}}'
```

Response 201: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string | App instance id. |
| `data.spaceId` | string | Workspace id. |
| `data.appKey` | string | The app type: a key from `GET /v1/apps/catalog` (`clock`, `weather`, `rss`, …). |
| `data.name` | string |  |
| `data.config` | any | The app's settings (JSON). The keys depend on `appKey`. |
| `data.nodeId` | string \| null | Home location, when one was set or derived. |
| `data.lastSnapshotKey` | string \| null |  |
| `data.lastSnapshotAt` | string \| null |  |
| `data.importSourceId` | string \| null |  |
| `data.createdAt` | string | ISO-8601 timestamp (UTC). |
| `data.updatedAt` | string | ISO-8601 timestamp (UTC). |
| `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. |

```json
{
  "data": {
    "id": "app_3c4d5e6f7a8b9c0d",
    "spaceId": "space_1a2b3c4d5e6f7a8b",
    "appKey": "clock",
    "name": "Lobby clock",
    "config": {
      "format": "24h"
    },
    "createdAt": "2026-09-28T09:00:00.000Z",
    "updatedAt": "2026-09-28T09:00:00.000Z",
    "deletedAt": null
  }
}
```

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: Missing `appKey`/`name`, invalid JSON config, unknown app (`unknown_app`), or a location outside this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/app-instances/{id}

Get an app instance

Retrieve one configured app instance, including its full configuration.

Auth: Bearer token. Permission: `app-instance.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | App instance id. |

```bash
curl "https://api.brixsignage.com/v1/app-instances/{id}" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | AppInstance | An installed, configured app. |
| `data.id` | string | App instance id. |
| `data.spaceId` | string | Workspace id. |
| `data.appKey` | string | The app type: a key from `GET /v1/apps/catalog` (`clock`, `weather`, `rss`, …). |
| `data.name` | string |  |
| `data.config` | any | The app's settings (JSON). The keys depend on `appKey`. |
| `data.nodeId` | string \| null | Home location; null = workspace root. |
| `data.lastSnapshotKey` | string \| null | Internal key of the last rendered thumbnail. |
| `data.lastSnapshotAt` | string \| null |  |
| `data.importSourceId` | string \| null | Id in the system it was imported from, if imported. |
| `data.createdAt` | string | ISO-8601 timestamp (UTC). |
| `data.updatedAt` | string | ISO-8601 timestamp (UTC). |
| `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such app instance in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## PATCH /v1/app-instances/{id}

Update an app instance

Edit a configured app's name, configuration, or node. `config` replaces the entire configuration, so read the current value first before submitting changes.

Auth: Bearer token. Permission: `app-instance.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | App instance id. |

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `appKey` | string | no | A key from `GET /v1/apps/catalog`. An unknown key is refused (422 `unknown_app`). |
| `name` | string | no |  |
| `config` | object \| string | no | The app's settings: a JSON object, or the same as a JSON string. Default `{}`. |
| `nodeId` | string \| null | no | Home location. Default: the caller's own location. |
| `baseUpdatedAt` | string | no | Optimistic concurrency: the `updatedAt` you read. A stale value is refused with 409 `conflict` and the `current` row. |

```bash
curl -X PATCH "https://api.brixsignage.com/v1/app-instances/{id}" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | AppInstance | An installed, configured app. |
| `data.id` | string | App instance id. |
| `data.spaceId` | string | Workspace id. |
| `data.appKey` | string | The app type: a key from `GET /v1/apps/catalog` (`clock`, `weather`, `rss`, …). |
| `data.name` | string |  |
| `data.config` | any | The app's settings (JSON). The keys depend on `appKey`. |
| `data.nodeId` | string \| null | Home location; null = workspace root. |
| `data.lastSnapshotKey` | string \| null | Internal key of the last rendered thumbnail. |
| `data.lastSnapshotAt` | string \| null |  |
| `data.importSourceId` | string \| null | Id in the system it was imported from, if imported. |
| `data.createdAt` | string | ISO-8601 timestamp (UTC). |
| `data.updatedAt` | string | ISO-8601 timestamp (UTC). |
| `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such app instance in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 409: `conflict`: the row changed since `baseUpdatedAt`; the body carries `current`.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: Invalid JSON config or unknown app.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## DELETE /v1/app-instances/{id}

Delete an app instance

Delete a configured app instance. Screens and playlists that reference it stop showing it.

Auth: Bearer token. Permission: `app-instance.delete`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | App instance id. |
| `force` | query | "true" | no | Delete even when it is shared into other places; the shares go with it. |

```bash
curl -X DELETE "https://api.brixsignage.com/v1/app-instances/{id}" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string |  |
| `data.deleted` | true |  |
| `data.sharesRemoved` | integer | Shares removed with it. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such app instance in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 409: `content_shared`: it is shared into other places; `shareCount`, `crossSpaceShares`, `contentShares` say where. Repeat with `?force=true` to delete it and those shares.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/app-instances/{id}/duplicate

Duplicate an app instance

Create a copy of a configured app instance, including its configuration and home node, named "<name> copy". Use this to reuse a tuned configuration instead of re-entering it. Requires permission to create app instances at the source instance's node.

**Notes.**
- The 201 body is the row as written, not re-read from the database, so columns the create does not set (for example `lastSnapshotAt`) are absent rather than null. `GET` returns every column.

Auth: Bearer token. Permission: `app-instance.create`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | App instance id. |

```bash
curl -X POST "https://api.brixsignage.com/v1/app-instances/{id}/duplicate" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 201: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string | App instance id. |
| `data.spaceId` | string | Workspace id. |
| `data.appKey` | string | The app type: a key from `GET /v1/apps/catalog` (`clock`, `weather`, `rss`, …). |
| `data.name` | string |  |
| `data.config` | any | The app's settings (JSON). The keys depend on `appKey`. |
| `data.nodeId` | string \| null | Home location, when one was set or derived. |
| `data.lastSnapshotKey` | string \| null |  |
| `data.lastSnapshotAt` | string \| null |  |
| `data.importSourceId` | string \| null |  |
| `data.createdAt` | string | ISO-8601 timestamp (UTC). |
| `data.updatedAt` | string | ISO-8601 timestamp (UTC). |
| `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such app instance in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/app-instances/{id}/restore

Restore a deleted app instance

Restore an app instance that was deleted within the last 30 days. The shares removed by the delete come back. Playlist items removed by the delete do not come back; add the app to those playlists again.

Auth: Bearer token. Permission: `app-instance.delete`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | App instance id. |

```bash
curl -X POST "https://api.brixsignage.com/v1/app-instances/{id}/restore" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string |  |
| `data.restored` | true |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such app instance in this workspace, or it was purged.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 409: `not_deleted`: it is not in the recycle bin.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/app-instances/{id}/thumbnail

Get an app instance thumbnail

Retrieve a shared preview image of an app instance. The same image is reused everywhere the app is previewed, so it loads quickly. Add `?fresh=1` to force a new image to be generated after a configuration change.

**Notes.**
- A cached image is `image/jpeg`. When there is none yet, the answer is a neutral `image/svg+xml` placeholder (header `x-brix-cache: pending`, not cached) while the image is drawn in the background; fetch it again shortly.

Auth: Bearer token. Permission: `app-instance.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | App instance id. |
| `fresh` | query | "1" | no | Skip the cached image and draw it again. |

```bash
curl "https://api.brixsignage.com/v1/app-instances/{id}/thumbnail" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such app instance in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 503: `browser_unavailable`: images cannot be drawn in this environment.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |
