# BI dashboards CLI commands

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

Connect Grafana, Tableau, Power BI or a web dashboard, then browse, create, test and show its views on screens. 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.

## BI connections

A BI connection is the project's access to a system it owns that shows dashboards: for Grafana its address and a service-account token, for Power BI a Microsoft Entra app (service principal) and its client secret, for Tableau a connected app and a user, and for a web page the service account of a site that shows a dashboard behind a sign-in, or the cookies of a signed-in browser. A connection ID starts `bic_`. Secrets are sealed on the server and never shown again: a read shows `secret` `{set, hint}`, where `hint` is the last four characters of a secret of 12 or more. Connections belong to one project. `bi connection list`, `show` and `browse` need Read access, and every other command needs Manage access. A plan without BI dashboards answers `feature_not_in_plan` (exit 8) to creating a connection or a view and to changing one, except replacing a credential; [pricing](https://screenrig.ai/pricing.md) lists the plans.

Give a secret in a file: `--token-file` for Grafana, `--secret-file` for Power BI and Tableau, `--password-file`, `--totp-file` and `--cookies-file` for a web page. Each takes `-` for stdin, never an argument, and so does a web connection's `--steps` script; at most one option of a command reads stdin.

A view plays one dashboard, report or page of a connection through an `iframe` primitive that shows its `embed_url`; `bi view create --connection-id ID` makes one. [Show a dashboard behind a sign-in](https://screenrig.ai/docs/compose.md#show-a-dashboard-behind-a-sign-in) walks through a web connection, and the [API reference](https://screenrig.ai/docs/api#group-bi) covers every kind.

### `bi connection create grafana`

Add a Grafana connection: its address and a service-account token.

```console
bi connection create grafana --url URL --token-file FILE [--name NAME] [--org-id N]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `--url URL` | string | yes | none | Grafana's address on a public host, with any path prefix: `https://grafana.example.com` or `https://example.com/grafana`. A private, loopback or reserved address is `invalid_request` (exit 8), and Grafana must answer without redirecting. |
| `--token-file FILE` | string | yes | none | Read the service-account token from this file, or `-` for stdin (never an argument); the file holds only the token. |
| `--name NAME` | string | no | the host name | 1 to 80 characters. |
| `--org-id N` | integer | no | `1` | The Grafana organization the token belongs to: a whole number from 1. |

```console
screenrig bi connection create grafana --url https://grafana.example.com --token-file token.txt
screenrig bi connection create grafana --url https://example.com/grafana --token-file - --name "Ops Grafana" --org-id 2
```

In Grafana, install the Grafana Image Renderer plugin or the remote rendering service (it draws dashboards as images; without it nothing renders), then create a service account with the Viewer role and add a token. Run `bi connection test` next.

### `bi connection create tableau`

Add a Tableau connection: a connected app (direct trust), the user the screens' views are shown as, and the app's secret value.

```console
bi connection create tableau --url URL --client-id ID --secret-id ID --username USER
    --secret-file FILE [--site SITE] [--name NAME]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `--url URL` | string | yes | none | The https address of Tableau Server, or of your Tableau Cloud pod such as `https://10ay.online.tableau.com` (the pod, not `online.tableau.com`). |
| `--site SITE` | string | no | Tableau Server's default site | The site's content URL, the part after `/site/` or `/t/` in a view's address. |
| `--client-id ID` | string | yes | none | The connected app's Client ID. |
| `--secret-id ID` | string | yes | none | The Secret ID of a secret generated for the connected app. |
| `--username USER` | string | yes | none | The Tableau user the screens' views are shown as (an email address on Tableau Cloud). |
| `--secret-file FILE` | string | yes | none | Read the secret's value from this file, or `-` for stdin (never an argument); the file holds only the secret value, not the Secret ID. |
| `--name NAME` | string | no | the host name | 1 to 80 characters. |

```console
screenrig bi connection create tableau --url https://10ay.online.tableau.com --site lobby --client-id 11111111-2222-3333-4444-555555555555 --secret-id 66666666-7777-8888-9999-000000000000 --username screens@example.com --secret-file secret.txt
```

Set up once in Tableau: under Settings, Connected Apps, create a Direct trust connected app, copy its Client ID, generate a secret and copy the Secret ID and the secret value, and enable the app and the secret. Pick or create the Tableau user the screens show and give it access to only the views screens show: anyone who holds a screen's ticket can embed any view that user can see. Then run `bi connection show ID` and enter the host of its `embed_origin` in the connected app's domain allowlist. It needs Tableau Cloud or Tableau Server 2023.3 or later; the screens must reach the Tableau host, and their browser must accept third-party cookies from it. Run `bi connection test` next, then `bi connection browse` to find a view.

### `bi connection create powerbi`

Add a Power BI connection: a Microsoft Entra app (service principal) and its client secret, or a Microsoft connection of this project.

```console
bi connection create powerbi
    (--tenant-id ID --client-id ID --secret-file FILE | --connection-id ID) [--name NAME]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `--tenant-id ID` | string | without `--connection-id` | none | Your Microsoft Entra Directory (tenant) ID, or its domain such as `contoso.onmicrosoft.com`; not `common`, `organizations` or `consumers`. Together with `--client-id` and `--secret-file`. |
| `--client-id ID` | string | without `--connection-id` | none | The Application (client) ID of the app registration. |
| `--secret-file FILE` | string | without `--connection-id` | none | Read the client secret's Value from this file, or `-` for stdin (never an argument); the file holds only the secret value, not the secret ID. |
| `--connection-id ID` | string | without `--tenant-id` | none | Sign in through a Microsoft connection (`pcn_…`) of this project in mode `own_app`, instead of `--tenant-id`, `--client-id` and `--secret-file`. A `screenrig_app` connection is refused. Not with them. |
| `--name NAME` | string | no | `Power BI` | 1 to 80 characters. |

```console
screenrig bi connection create powerbi --tenant-id 11111111-2222-3333-4444-555555555555 --client-id aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --secret-file secret.txt
screenrig bi connection create powerbi --connection-id pcn_01EXAMPLECONNECTION0000 --name "Contoso Power BI"
```

Set up once. In Microsoft Entra, create an app registration (single tenant, no redirect URI, no API permissions) and a client secret. A Power BI admin turns on, in the admin portal under Tenant settings, Developer settings, Embed content in apps and Service principals can call Fabric public APIs, for the whole organization or for a security group that holds the app. Add the app to each workspace you show with the Member or Admin role; Member is the least access. For production embedding the workspace must sit on a Fabric (F), Power BI Embedded (A) or Premium (P/EM) capacity. Commercial cloud only. Run `bi connection test` next, then `bi connection browse` to find a workspace and a report.

### `bi connection create web`

Add a web connection: a service account of a site that shows a dashboard behind a sign-in, or the cookies of a signed-in browser.

```console
bi connection create web --login-mode auto|steps|none [--login-url URL] [--username USER]
    [--password-file FILE] [--totp-file FILE] [--steps FILE] [--cookies-file FILE] [--name NAME]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `--login-mode MODE` | string | yes | none | `auto` fills the form on `--login-url` with `--username` and the password. `steps` runs the `--steps` script. `none` signs nothing in, so pages show with the `--cookies-file` jar and takes none of `--login-url`, `--username`, `--password-file`, `--totp-file` or `--steps`. |
| `--login-url URL` | string | with `auto` or `steps` | none | The https address of the site's sign-in page, on a public host. The password is typed only there and in the script's `goto` steps, never into a view's address. |
| `--username USER` | string | with `auto` | none | The service account's username, which a `steps` script reads as `{{username}}`. |
| `--password-file FILE` | string | with `auto` | none | Read the service account's password from this file, or `-` for stdin (never an argument): spaces are allowed, and trailing line endings are dropped. |
| `--totp-file FILE` | string | no | none | Read the base32 key the site shows for the account's authenticator app (16 to 128 characters), or its `otpauth://totp` address, from this file or `-`: the server computes the codes. |
| `--steps FILE` | string | with `steps` | none | The sign-in script, a JSON array of at most 30 steps, from a file or `-`. Actions: `goto` (`url`), `fill` (`selector`, `value`), `click` and `wait_for` (`selector`), `press` (`value` such as `Enter`, optional `selector`) and `wait_ms` (`ms`, 1 to 10000). A value may hold `{{username}}`, `{{password}}` and `{{otp}}`, never the secret itself. Not with `auto`. |
| `--cookies-file FILE` | string | no | none | A cookie jar exported from a signed-in browser: a JSON array of cookies (a cookie extension's export) or an object with a `cookies` array (Playwright's storage state), from a file or `-`; at most 100. Only `name`, `value`, `domain`, `path`, `expires`, `httpOnly`, `secure` and `sameSite` are sent. |
| `--name NAME` | string | no | the sign-in host, or `Web page` with `--login-mode none` | 1 to 80 characters. |

```console
screenrig bi connection create web --login-mode auto --login-url https://login.example.com/signin --username screens@example.com --password-file password.txt --name "Ops dashboard"
screenrig bi connection create web --login-mode steps --login-url https://login.example.com/signin --username screens@example.com --password-file password.txt --steps steps.json
screenrig bi connection create web --login-mode none --cookies-file cookies.json --name "Signed-in jar"
```

The password, the key and the cookies are sealed and never shown again: `data.secret` and `data.totp_secret` say whether they are set, and `data.session` says where the sign-in stands (`idle`, `active`, `login_needed`, `paused`, `stopped` or `expired`). Use a read-only service account. A refused sign-in waits 5 minutes, then 15, 60 and 120, and the fifth in a row stops the connection until its credentials or sign-in settings change. A site that approves sign-ins on a phone, checks devices or shows a CAPTCHA cannot be scripted: use a cookie jar. A server without the page renderer answers `bi_web_unavailable` (exit 5). Run `bi connection test` next.

### `bi connection list`

List this project's BI connections with their kind, masked secret and last test.

```console
bi connection list
```

No parameters.

```console
screenrig bi connection list
```

`data.items` holds every connection of the project; no secret is returned.

### `bi connection show`

Inspect one connection and its last test.

```console
bi connection show <id>
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<id>` | string | yes | none | The connection (`bic_…`). |

```console
screenrig bi connection show bic_01EXAMPLECONNECTION0000
```

The answer carries `kind`, `config` (`url` and `org_id` for Grafana; `tenant_id` and `client_id` for Power BI, or `connection_id` when a Microsoft connection signs in; `url`, `site`, `client_id`, `secret_id` and `username` for Tableau; `login_mode`, `login_url`, `username` and `steps` for a web connection), `secret` `{set, hint}`, `embed_origin` (the project's embed host, which a connected app's domain allowlist names) and `last_test`. A web connection also carries `totp_secret` `{set}` and `session`: its `state`, the `cookies` count and `cookies_updated_at`, `last_login_at`, `login_failures` and `retry_at`.

### `bi connection update`

Change a connection's name, address, organization, tenant, app, site, user, secret or, for a web connection, its sign-in and cookies.

```console
bi connection update <id> [--name NAME] [--url URL] [--org-id N] [--tenant-id ID] [--client-id ID]
    [--site SITE | --default-site] [--secret-id ID] [--username USER]
    [--token-file FILE | --secret-file FILE | --password-file FILE | --clear-password]
    [--login-mode auto|steps|none] [--login-url URL] [--totp-file FILE | --clear-totp]
    [--steps FILE] [--cookies-file FILE | --clear-cookies] [--expect-rev REVISION]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<id>` | string | yes | none | The connection (`bic_…`). |
| `--name NAME` | string | no | unchanged | Rename the connection. |
| `--url URL` | string | no | unchanged | The new address of Grafana, or of the Tableau server or pod. |
| `--org-id N` | integer | no | unchanged | The Grafana organization the token belongs to. |
| `--tenant-id ID` | string | no | unchanged | The Power BI connection's Microsoft Entra tenant. |
| `--client-id ID` | string | no | unchanged | The Power BI connection's Application (client) ID, or the Tableau connected app's Client ID. |
| `--site SITE` | string | no | unchanged | The Tableau connection's site content URL. Not with `--default-site`. |
| `--default-site` | boolean | no | off | Tableau Server: sign in to the default site. Not with `--site`. |
| `--secret-id ID` | string | no | unchanged | The Tableau connected app's Secret ID. |
| `--username USER` | string | no | unchanged | The Tableau user the screens' views are shown as, or the web connection's service account. |
| `--token-file FILE` | string | no | unchanged | Grafana: read the service-account token from this file, or `-` for stdin; it replaces the stored token. At most one of `--token-file`, `--secret-file`, `--password-file` and `--clear-password`. |
| `--secret-file FILE` | string | no | unchanged | Power BI or Tableau: read the secret value from this file, or `-` for stdin; it replaces the stored secret. |
| `--login-mode MODE` | string | no | unchanged | Web: how it signs in, `auto`, `steps` or `none`. |
| `--login-url URL` | string | no | unchanged | Web: the https address of the site's sign-in page. A new origin needs `--password-file` in the same command, so the stored password is never sent to a page you did not give it. |
| `--password-file FILE` | string | no | unchanged | Web: read the service account's password from this file, or `-` for stdin; it replaces the stored password. |
| `--clear-password` | boolean | no | off | Web: remove the stored password. |
| `--totp-file FILE` | string | no | unchanged | Web: read the authenticator key from this file, or `-` for stdin; it replaces the stored key. Not with `--clear-totp`. |
| `--clear-totp` | boolean | no | off | Web: remove the stored authenticator key. Not with `--totp-file`. |
| `--steps FILE` | string | no | unchanged | Web: replace the sign-in script, a JSON array of at most 30 steps, from a file or `-`. |
| `--cookies-file FILE` | string | no | unchanged | Web: replace the cookie jar with one exported from a signed-in browser, from a file or `-` (the forms `bi connection create web` takes). Not with `--clear-cookies`. |
| `--clear-cookies` | boolean | no | off | Web: remove the stored cookie jar. Not with `--cookies-file`. |
| `--expect-rev REVISION` | integer | no | none | Refuse the change unless the connection is at this revision (`revision_conflict`, exit 6). |

```console
screenrig bi connection update bic_01EXAMPLECONNECTION0000 --token-file new-token.txt
screenrig bi connection update bic_01EXAMPLECONNECTION0000 --password-file new-password.txt
screenrig bi connection update bic_01EXAMPLECONNECTION0000 --clear-totp --clear-cookies
screenrig bi connection update bic_01EXAMPLECONNECTION0000 --url https://grafana.example.com --token-file new-token.txt --name "Ops Grafana" --expect-rev 2
```

At least one change is required, and an omitted secret is kept. A new address on another host, scheme or port needs `--token-file` (Grafana) or `--secret-file` (Tableau) in the same command: the stored secret is only sent to the address it was saved for. A new address, organization, tenant, app, site, user or secret clears `last_test` and retires every render cached for the connection's views: test it again. A change of credentials or sign-in settings lifts a stopped web session.

### `bi connection test`

Check the credentials: Grafana's token and Image Renderer, Power BI's app and its workspaces, Tableau's sign-in, or a web connection's sign-in.

```console
bi connection test <id>
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<id>` | string | yes | none | The connection (`bic_…`). |

```console
screenrig bi connection test bic_01EXAMPLECONNECTION0000
```

Grafana: `token` (Grafana accepts it) and `image_renderer` (the plugin is listed; Grafana without it only warns, because a remote rendering service does not show as a plugin). Power BI: `token` (Microsoft Entra issued one for the app) and `workspaces` (the workspaces the app can see; none fails, and a refusal names the Developer tenant settings and the app's Member or Admin role in the workspace). Tableau: `signin` as the connection's user with a content-read token, followed by a sign-out. Web: `login` signs in as the service account and draws its sign-in page; a web connection that signs nothing in has no sign-in to test, so it answers a warning, and `bi view test` checks its cookies. A refusal is a failed check that carries the vendor's own answer, and the result is stored as `last_test`. The test does not judge a Tableau domain allowlist, capacity, row-level security or a dashboard's permissions: `bi view test` checks a view for real.

### `bi connection browse`

List what a connection can show: Grafana dashboards and panels, Power BI workspaces and reports, or Tableau views.

```console
bi connection browse <id> [--query TEXT] [--dashboard UID] [--workspace ID]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<id>` | string | yes | none | The connection (`bic_…`). |
| `--query TEXT` | string | no | none | Only dashboards, workspaces, reports or views whose name matches, at most 200 characters. |
| `--dashboard UID` | string | no | none | Grafana: list this dashboard's panels instead. Not with `--workspace`. |
| `--workspace ID` | string | no | none | Power BI: list this workspace's reports instead, by its GUID. Not with `--dashboard`. |

```console
screenrig bi connection browse bic_01EXAMPLECONNECTION0000 --query lobby
screenrig bi connection browse bic_01EXAMPLECONNECTION0000 --dashboard abc123
screenrig bi connection browse bic_01EXAMPLECONNECTION0000 --workspace f089354e-8366-4e18-aea3-4cb4a3a50b48 --query sales
```

`data.items` lists, for Grafana, the `uid`, `title`, `folder` and `tags` of each dashboard the token can show, or with `--dashboard` the `id`, `title` and `type` of its panels (rows are flattened, so a panel inside a collapsed row is listed); for Power BI the `id` and `name` of each workspace the app can see, or with `--workspace` the `id`, `name` and `embed_url` of its reports; for Tableau the `path`, as `Workbook/View`, and `name` of each view the connection's user can see. Use these values with `bi view create`. A web connection lists nothing: each web view takes the address of its page.

### `bi connection delete`

Delete a connection no view uses.

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

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<id>` | string | yes | none | The connection (`bic_…`). |
| `--expect-rev REVISION` | integer | no | none | Refuse the delete unless the connection is at this revision (`revision_conflict`, exit 6). |

```console
screenrig bi connection delete bic_01EXAMPLECONNECTION0000
```

While views use it the answer is `resource_conflict` (exit 5), naming them: delete those views first. The answer is `data.id` and `data.deleted` `true`.

## BI views

A view is one dashboard or panel of a Grafana connection, one report of a Power BI workspace, one view of a Tableau site, one page of a web connection, or a public link that needs no connection: a Grafana public dashboard, a Power BI Publish to web link or a Tableau Public viz. A view ID starts `biv_`, and a view plays only on screens of its own project.

Put the view's `embed_url` in an `iframe` primitive with `content_fit` `fill` and publish as usual: the playlist holds no credential. Grafana and web views are drawn on the server as an image every `--refresh` and shared by every screen of the same size, so ten lobby screens make one render and the screen never talks to the system; the last good image stays on glass while a capture fails. A Power BI report and a Tableau view embed with a view-only token the server generates and renews, and the screen never holds the secret. A picture is drawn only while a screen's manifest names the view.

Reading needs Read access, and every change needs Manage access. Each target flag belongs to one kind of connection, and `bi connection browse ID` lists the values to use.

### `bi view create`

Add a view of a connection's dashboard, panel, report or Tableau view, or of a public link.

```console
bi view create (--connection-id ID (--url URL | --dashboard UID | --workspace ID --report ID | --view WORKBOOK/VIEW)
    | --public-url URL) [--name NAME] [--refresh DURATION] [--fit contain|cover|fill] [--rotate DURATION]
    [--panel ID] [--from TIME] [--to TIME] [--var NAME=VALUE]... [--theme dark|light] [--timezone ZONE]
    [--page NAME]... [--all-pages] [--rotate-sheets] [--wait-for CSS] [--wait-ms MS] [--clip CSS]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `--connection-id ID` | string | without `--public-url` | none | The connection (`bic_…`) to show something of. |
| `--url URL` | string | one target | none | A dashboard address on the connection's Grafana host, a Power BI report address, a Tableau view address or, for a web connection, the page's https address on a public host. The rest of the target is read from it, so it takes none of `--panel`, `--from`, `--to`, `--var`, `--theme`, `--timezone`, `--page` or `--all-pages`. |
| `--dashboard UID` | string | one target | none | Grafana: the dashboard's uid, the part after `/d/` in its address. |
| `--workspace ID` | string | one target | none | Power BI: the workspace ID, with `--report`. |
| `--report ID` | string | with `--workspace` | none | Power BI: the report ID, with `--workspace`. |
| `--view WORKBOOK/VIEW` | string | one target | none | Tableau: the view, the names after `/views/` in its address (`bi connection browse` lists them). |
| `--public-url URL` | string | one target | none | A Grafana public dashboard link, a Power BI Publish to web link or a Tableau Public viz address. It needs no connection and plays live from its host. Give it alone, with `--name`, `--refresh`, `--fit`, `--rotate-sheets` and `--rotate`. |
| `--name NAME` | string | no | from the target | 1 to 80 characters. |
| `--refresh DURATION` | string | no | `5m` | Grafana: how often the image is drawn again, `30s` to `24h`. Web: how often the page is drawn again, `60s` to `24h`. Power BI and Tableau: how often the report or view refreshes its data, `60s` to `24h`, or `0` for none. Give a duration such as `30s`, `5m` or `1h`, or a number of seconds. |
| `--fit FIT` | string | no | `contain` | Grafana and web: how the image fills the screen, `contain`, `cover` or `fill`. |
| `--rotate DURATION` | string | no | none | Power BI: how long each page shows, `10s` to `1h`; needed with two or more `--page` or with `--all-pages`. Tableau: how long each sheet shows; needed with `--rotate-sheets`. |
| `--page NAME` | string | no | none | Power BI: a page to show, by name or display name. 1 to 200 characters. Repeatable; the pages show in this order. |
| `--all-pages` | boolean | no | off | Power BI: rotate through every visible page, with `--rotate`. |
| `--rotate-sheets` | boolean | no | off | Tableau: turn the workbook's published sheets, with `--rotate`. |
| `--panel ID` | string | no | the whole dashboard | Grafana: show this one panel instead of the whole dashboard. A whole number, as in the address's `viewPanel`. |
| `--from TIME` | string | no | none | Grafana: start of the time range, such as `now-6h`, `now/d` or an epoch in milliseconds. |
| `--to TIME` | string | no | none | Grafana: end of the time range, such as `now`, `now/d` or an epoch in milliseconds. |
| `--var NAME=VALUE` | string | no | none | Grafana: a dashboard variable. `NAME` may carry a `var-` prefix and a value is at most 200 characters. Repeatable. |
| `--theme THEME` | string | no | `dark` | Grafana theme, `dark` or `light`. |
| `--timezone ZONE` | string | no | none | Grafana: time zone for the dashboard, such as `Europe/Paris`, `utc` or `browser`. |
| `--wait-for CSS` | string | no | none | Web: a CSS selector of an element that shows when the page is ready, and only when it is signed in. The capture waits for it and fails with `bi_web_timeout` when it never shows. |
| `--wait-ms MS` | integer | no | none | Web: milliseconds to wait after the page is ready, for late charts, 0 to 10000. |
| `--clip CSS` | string | no | the whole page | Web: a CSS selector of the part of the page to draw. |

```console
screenrig bi view create --connection-id bic_01EXAMPLECONNECTION0000 --url 'https://grafana.example.com/d/abc123/lobby?orgId=1&viewPanel=4' --refresh 5m
screenrig bi view create --connection-id bic_01EXAMPLECONNECTION0000 --dashboard abc123 --panel 4 --from now-6h --to now --var host=web1 --theme light --name "Web CPU"
screenrig bi view create --public-url https://grafana.example.com/public-dashboards/TOKEN --name "Public status"
screenrig bi view create --connection-id bic_01EXAMPLECONNECTION0000 --workspace f089354e-8366-4e18-aea3-4cb4a3a50b48 --report 5b218778-e7a5-4d73-8187-f10824047715 --page Overview --page Detail --rotate 30s --name "Sales rotation"
screenrig bi view create --connection-id bic_01EXAMPLECONNECTION0000 --view Sales/Overview --rotate-sheets --rotate 30s --refresh 0 --name "Sales sheets"
screenrig bi view create --connection-id bic_01EXAMPLECONNECTION0000 --url https://app.example.com/dashboards/42 --refresh 5m --wait-for '#dashboard.loaded' --wait-ms 1500 --clip '#chart-grid'
```

Give exactly one target: `--url`, `--dashboard`, `--workspace` with `--report`, `--view` or `--public-url`. The answer carries `data.embed_url`. Run `bi view test` next.

Grafana: `--url` reads `orgId`, `viewPanel` or `panelId`, `from`, `to`, `var-*`, `theme` and `timezone` from the address. The image is drawn through Grafana's render service and shared by every screen of the same size, and a screen keeps the last image, with the render's time in a corner, while Grafana is down. A public dashboard link plays live from Grafana, which must allow embedding (`allow_embedding = true` in its settings): `bi view test` checks it.

Power BI: `--url` takes a report address copied from Power BI, `https://app.powerbi.com/groups/WORKSPACE/reports/REPORT`, with an optional `/ReportSection` page; or give `--workspace` and `--report`. Two or more pages need `--rotate`, and `--rotate` needs two or more pages or `--all-pages`. `--refresh 0` suits a DirectQuery or automatic-page-refresh report that updates itself. `--fit` does not apply. A Publish to web link shows the report to anyone with the link and plays as Power BI serves it, reloaded every `--refresh`, without page rotation.

Tableau: `--url` takes a view address copied from Tableau, `https://HOST/t/SITE/views/WORKBOOK/VIEW`, Tableau Cloud's `https://HOST/#/site/SITE/views/WORKBOOK/VIEW`, or just `WORKBOOK/VIEW`; the address must be on the connection's host and site. `--rotate-sheets` and `--rotate` each need the other. `--fit` does not apply. The screens must reach the Tableau host and accept its third-party cookies, and the view's `embed_origin` must be in the connected app's domain allowlist. A Tableau Public viz (`--public-url https://public.tableau.com/app/profile/USER/viz/WORKBOOK/VIEW`, or its `/views/` address) needs no connection and no token, and `--rotate-sheets` works there too.

Web: `--url` is the page's address, and `--wait-for`, `--wait-ms` and `--clip` apply only to a web view. The last good image stays on glass when a capture fails, and `status.last_error` says why: `bi_web_login_failed`, `bi_web_otp_required`, `bi_web_session_expired`, `bi_web_login_paused`, `bi_web_timeout`, `bi_web_navigation_failed` or `bi_web_blocked`.

### `bi view list`

List this project's BI views with their target, cadence and status.

```console
bi view list
```

No parameters.

```console
screenrig bi view list
```

`data.items` holds every view of the project, each with its `embed_url`.

### `bi view show`

Inspect one view, its `embed_url` and how it last fared on glass.

```console
bi view show <id>
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<id>` | string | yes | none | The view (`biv_…`). |

```console
screenrig bi view show biv_01EXAMPLEVIEW0000000000
```

The answer carries `kind`, `name`, `connection_id` (absent for a public link), `target` (`view` and `rotate_sheets` for Tableau), `refresh_s`, `rotate_s`, `fit`, `status`, `embed_url` and `revision`; a web view also carries `wait_for`, `wait_ms` and `clip`. `status` holds `last_ok_at` and `last_error` with a `code` and a `detail`: `bi_upstream_refused` when Grafana refused the token, `bi_upstream_unavailable` when it could not render, or one of the `bi_web_*` codes of a web view.

### `bi view update`

Change a view's name, target, cadence or fit.

```console
bi view update <id> [--name NAME] [--refresh DURATION] [--fit contain|cover|fill] [--rotate DURATION]
    [--url URL | --dashboard UID | --workspace ID | --view WORKBOOK/VIEW | --public-url URL] [--report ID]
    [--page NAME]... [--all-pages] [--rotate-sheets] [--panel ID] [--from TIME] [--to TIME]
    [--var NAME=VALUE]... [--theme dark|light] [--timezone ZONE] [--wait-for CSS | --clear-wait-for]
    [--wait-ms MS] [--clip CSS | --clear-clip] [--expect-rev REVISION]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<id>` | string | yes | none | The view (`biv_…`). |
| `--name NAME` | string | no | unchanged | Rename the view. |
| `--url URL` | string | no | unchanged | A dashboard, report or Tableau view address, or a web view's page address; replaces the whole target. Not with the other target flags. |
| `--dashboard UID` | string | no | unchanged | Grafana: show this dashboard; replaces the whole target. |
| `--workspace ID` | string | no | unchanged | Power BI: show a report of this workspace, with `--report`; replaces the whole target. |
| `--report ID` | string | no | unchanged | Power BI: show this report, in `--workspace` or the current one. |
| `--view WORKBOOK/VIEW` | string | no | unchanged | Tableau: show this view; replaces the whole target. |
| `--public-url URL` | string | no | unchanged | A public link; replaces the whole target. |
| `--rotate-sheets` | boolean | no | off | Tableau: turn the workbook's published sheets, with `--rotate`. |
| `--page NAME` | string | no | unchanged | Power BI: the pages to show, by name or display name. Repeatable; it replaces the current pages. |
| `--all-pages` | boolean | no | off | Power BI: rotate through every visible page. |
| `--rotate DURATION` | string | no | unchanged | Power BI: how long each page shows, `10s` to `1h`, or `0` for one page. Tableau: how long each sheet shows, or `0` to stop turning them. |
| `--panel ID` | string | no | unchanged | Grafana: show this one panel. |
| `--from TIME` | string | no | unchanged | Grafana: start of the time range. |
| `--to TIME` | string | no | unchanged | Grafana: end of the time range. |
| `--var NAME=VALUE` | string | no | unchanged | Grafana: set a dashboard variable. Repeatable; it sets one variable and keeps the others. |
| `--theme THEME` | string | no | unchanged | Grafana theme, `dark` or `light`. |
| `--timezone ZONE` | string | no | unchanged | Grafana: time zone for the dashboard. |
| `--refresh DURATION` | string | no | unchanged | How often the image is drawn again or the report refreshes: Grafana `30s` to `24h`; web `60s` to `24h`; Power BI and Tableau `60s` to `24h`, or `0` for none. |
| `--fit FIT` | string | no | unchanged | Grafana and web: how the image fills the screen, `contain`, `cover` or `fill`. |
| `--wait-for CSS` | string | no | unchanged | Web: the element that shows when the page is ready and signed in. Not with `--clear-wait-for`. |
| `--clear-wait-for` | boolean | no | off | Web: stop waiting for an element. Not with `--wait-for`. |
| `--wait-ms MS` | integer | no | unchanged | Web: milliseconds to wait after the page is ready, 0 to 10000; `0` removes the wait. |
| `--clip CSS` | string | no | unchanged | Web: the part of the page to draw. Not with `--clear-clip`. |
| `--clear-clip` | boolean | no | off | Web: draw the whole page again. Not with `--clip`. |
| `--expect-rev REVISION` | integer | no | none | Refuse the change unless the view is at this revision (`revision_conflict`, exit 6). |

```console
screenrig bi view update biv_01EXAMPLEVIEW0000000000 --wait-for '#dashboard.loaded' --wait-ms 2000
screenrig bi view update biv_01EXAMPLEVIEW0000000000 --clear-wait-for --clear-clip
screenrig bi view update biv_01EXAMPLEVIEW0000000000 --theme light --var host=web2
screenrig bi view update biv_01EXAMPLEVIEW0000000000 --dashboard def456 --panel 2 --expect-rev 3
screenrig bi view update biv_01EXAMPLEVIEW0000000000 --page Overview --page Detail --rotate 45s
screenrig bi view update biv_01EXAMPLEVIEW0000000000 --view Sales/Detail
```

At least one change is required. `--url`, `--dashboard`, `--workspace` and `--public-url` replace the whole target, while `--panel`, `--from`, `--to`, `--var`, `--theme` and `--timezone` change the view's current Grafana target, `--report`, `--page` and `--all-pages` the Power BI one, and `--view` and `--rotate-sheets` the Tableau one. `--page` replaces the page list; to clear it give `--workspace` and `--report` again with `--rotate 0`. `--rotate 0` alone stops a Tableau view from turning its sheets. `--url` and `--public-url` take no other target flag but `--rotate-sheets`, and a view of a public link changes its target only with `--public-url`. A new target clears the status and retires its cached renders, and screens pick the change up at their next refresh without a republish. A view keeps its connection and kind.

### `bi view test`

Check a view for real: a Grafana or web render, a Power BI embed token, a Tableau lookup, or a public link.

```console
bi view test <id> [--output FILE [--width PIXELS] [--height PIXELS]]
```

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<id>` | string | yes | none | The view (`biv_…`). |
| `--output FILE` | string | no | none | Grafana or web: save the rendered PNG here instead of running the checks. A Power BI or Tableau view and a public link have none (`not_found`). |
| `--width PIXELS` | integer | no | `1920` | Width of the saved image, 1 to 10000. Needs `--output`. |
| `--height PIXELS` | integer | no | `1080` | Height of the saved image, 1 to 10000. Needs `--output`. |

```console
screenrig bi view test biv_01EXAMPLEVIEW0000000000
screenrig bi view test biv_01EXAMPLEVIEW0000000000 --output lobby.png --width 1920 --height 1080
```

Grafana: a connection view renders for real, outside the shared cache and without touching the view's status. `data.ok` is false with Grafana's reason when it refuses the token, has no renderer or does not know the dashboard; a failed test is data, exit 0. Web: a connection view is drawn once in the server's browser (`render`), outside the shared cache, and a failed draw names why. Open the saved PNG and look before publishing. For a public link the test fetches the page and checks that Grafana lets the view's embed page frame it.

Tableau: a connection view signs in as the connection's user and looks the view up (`signin`, `view`); a view the user cannot see, or a name with the wrong case, is a failed `view` check naming the path. A Tableau Public view asks Tableau Public whether the workbook exists. Power BI: a connection view reads the report (`report`) and generates a real view-only embed token for it (`embed_token`); Power BI's own reason is shown when it refuses, such as a dataset with row-level security, a 401 or 403 (tenant setting or workspace role) or a report the app cannot see. A Publish to web link is fetched and its framing headers read.

The test does not judge what the picture shows, the pages the view names, capacity or a Tableau domain allowlist. `data` holds `at`, `ok` and `checks`, each check with `name`, `ok`, an optional `warn` and a `detail`; a Grafana or web render adds `width`, `height` and `bytes`. With `--output`, `data` holds `id`, `file`, `bytes`, `width` and `height` instead.

### `bi view delete`

Delete a view.

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

| Parameter | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `<id>` | string | yes | none | The view (`biv_…`). |
| `--expect-rev REVISION` | integer | no | none | Refuse the delete unless the view is at this revision (`revision_conflict`, exit 6). |

```console
screenrig bi view delete biv_01EXAMPLEVIEW0000000000
screenrig bi view delete biv_01EXAMPLEVIEW0000000000 --expect-rev 3
```

A screen still showing the view's `embed_url` answers `not_found` until its playlist changes. The answer is `data.id` and `data.deleted` `true`.

## 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)
- [Applications](https://screenrig.ai/docs/cli/applications.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)
- [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)
