Operations
Uploads and publications run as operations. media upload, media replace and app upload wait for theirs by default; these commands are how you finish one you left running with --no-wait.
operations show#
Read one operation's current state.
operations show <id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The operation id from the envelope that started it. |
screenrig operations show op_01EXAMPLEOPERATION00000state is receiving, queued, running, succeeded, failed or cancelled; the last three end the operation. A succeeded operation carries its result, and a failed one carries error with the problem code and detail.
operations wait#
Poll one operation until it finishes.
operations wait <id> [--timeout MS] [--poll-ms MS]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The operation id. |
--timeout MS | integer | no | 120000 | Deadline for the wait. It also bounds each underlying request. |
--poll-ms MS | integer | no | 1000 | Interval between operation polls while waiting. |
screenrig operations wait op_01EXAMPLEOPERATION00000The wait ends when the operation is succeeded, failed or cancelled. A failed or cancelled operation exits 13 with its own error. A wait that runs out of time exits 11 and the operation keeps running; the error's next is operations wait ID, which resumes the wait without starting it again.
operations cancel#
Ask the control plane to cancel one operation.
operations cancel <id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The operation id. |
screenrig operations cancel op_01EXAMPLEOPERATION00000Cancelling needs Manage access. The answer is the cancelled operation.
Events
The project event log records what the project did and what the fleet did. A published release appends application.published; an app that calls screenrig.emit(code) appends its own event, which is the most direct evidence that it ran on a screen.
Presence is in the log too. screen.offline is written once a screen has been offline for 60 seconds, and screen.online when it comes back; a reconnect inside those 60 seconds writes neither. screen.offline carries details.offline_since and, when the screen has been online before, details.last_online_at. screen.online carries details.last_online_at, plus details.offline_at when the screen was offline before it returned. Like every screen.* event, they are free on the listen stream, so an agent can keep events follow open to watch a fleet.
A Player's content updates arrive as screen.manifest_changed, screen.manifest_activated and screen.manifest_upgrade, whose details carry manifest_revision, state and, when present, code and attempt. A failed upgrade has error severity, and retrying, blocked and partial are warnings.
events list#
Read one finite page of project events.
events list [--after CURSOR] [--limit N] [--type TYPE] [--source project|log] [--screen-id ID] [--playlist-id ID] [--app-id ID]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--after CURSOR | string | no | the first page | Resume after the next_cursor of the previous page. |
--limit N | integer | no | 50 | Page size, 1 to 200. |
--type TYPE | string | no | every type | One event type, or a family by a prefix ending in a dot, such as note.. |
--source SOURCE | string | no | project | project lists project events. log lists Player log records, which belong to a screen: use it with --screen-id. |
--screen-id ID | string | no | every screen | Events whose resource is this screen, including SDK emits. With --source log, that screen's Player log records. |
--playlist-id ID | string | no | every playlist | Events whose resource is this playlist, and events that name it in details.playlist_id, such as a screen taking it. Not with --source log. |
--app-id ID | string | no | every application | Application resource, release and K/V events; excludes SDK emits. Not with --source log. |
screenrig events list --limit 25
screenrig events list --type application.event --screen-id scr_SCREEN
screenrig events list --playlist-id pl_PLAYLIST
screenrig events list --source log --screen-id scr_SCREENSDK emit() and emitConfirmed() append application.event records whose resource is the screen. Read them with --type application.event, optionally adding --screen-id. The returned details.primitive_id and details.code identify the emitting primitive and named action. --app-id selects application resource, release and K/V events, so it excludes these screen events.
--source log reads the Player log: the conditions a Player reports, kept for the project's event retention. It has no sequence, so it is not on events follow and is not delivered to webhooks, and --playlist-id and --app-id are refused with it (usage_error, exit 2). An id of the wrong kind is refused before any request. data.next_cursor is null at the end of the history.
The default output is one JSON envelope for the page. With --human, output is one logfmt line per event: at=, type=, severity=, and scalar details.
events follow#
Stay on the event stream and print events as they arrive.
events follow [--after CURSOR] [--timeout MS] [--type TYPE]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--after CURSOR | string | no | the live tail | Resume from this cursor. |
--timeout MS | integer | no | no deadline | Ends the whole follow, including reconnect backoff. |
--type TYPE | string | no | every type | One event type, or a family by a prefix ending in a dot, such as note.. |
screenrig events followA disconnect or transient failure reconnects with backoff and resumes from the last event id. Replay after a reconnect is free. Authorization and not-found problems stop the command rather than retrying it.
With --type, the stream still carries every event and the CLI prints only the matching ones, and the stream.* frames that describe the stream itself always print: screenrig events follow --type note. watches project notes.
The default output is NDJSON: one envelope per event on its own line, rather than a single envelope. Use --human for logfmt lines.
Notifications
A notification tells a person, or another system, when a screen changes state. An email notification sends each recipient an email, a webhook notification sends a signed HTTPS POST, and a Slack, Teams, PagerDuty or Telegram notification posts a message to a chat or pager. An organization has at most 50 of every channel together across its projects, and every project starts with one email to its owner when a screen has been offline for 10 minutes.
A notification watches the conditions you name:
| Condition | Starts when | Ends when |
|---|---|---|
screen_offline | The Player has been offline for 60 seconds. | The screen is back online. |
display_disconnected | The Player reports its display disconnected. | The display reconnects. |
running_hot | The device reports a temperature at or above the hot threshold. | The temperature returns to normal. |
on_battery | A device with a battery reports it runs on the battery, unplugged. | The device reports another power source. |
storage_full | The Player's storage cannot hold its content. | The shortfall clears. |
content_stuck | The screen's content update is retrying, failed, blocked or partial. | The update reaches any other state. |
not_playing | The Player shows an error instead of its content. Full storage is storage_full. | The Player's status is no longer an error. |
loop_stalled | A screen that is online, on schedule and has current content has started no image or video for longer than its loop allows. | An image or video starts again, or screenRIG stops judging the screen. |
glass_blank | The screenshots screenRIG took over a run that covers the whole loop were all one flat tone. | Two screenshots in a row show a picture, or screenRIG stops judging the screen. |
glass_frozen | The screenshots screenRIG took over a run that covers the whole loop were all the same picture. | Two screenshots in a row show a moving picture, or screenRIG stops judging the screen. |
crashing | The Player crashed or restarted its renderer repeatedly. | Never: each spike is its own notice. |
source_failed | A data source's refresh failed. | A refresh works. |
source_stale | A data source kept failing until its data expired. | A refresh works. |
card_data_stale | A card's data, written with an expiry, reached it while a screen shows the card and nothing renewed it. | A write to the card's data lands. |
reach_dropped | A campaign reaches fewer screens than when it was last published, because screens left its tags or folders, or were archived or deleted. | The campaign reaches as many screens again, or it is published again. |
group_empty | A tag or folder in a campaign's audience matches no screen. | The tag or folder matches a screen again, or a publish removes it from the audience. |
A condition notifies once it has lasted --hold minutes (default 10), so a quick reconnect stays quiet. If it ends first, nothing is sent; if it ends after a notice went out, a resolved notice follows. crashing is sent at once. A notice is also skipped when someone acknowledges or snoozes the screen's alert during the hold, or when a mute covers the screen. --tag limits a notification to screens carrying that tag; without it, every screen counts.
Each notice has a severity. An opened notice is error for screen_offline and warning for every other condition, and a resolved notice or a test is info. --min-severity LEVEL (info, warning or error) creates no opened notice below that level, and a resolved notice follows its opened one. --severity CONDITION=LEVEL, repeatable, sets one condition's level for this notification, such as --severity not_playing=error; it counts for --min-severity. Teams and PagerDuty always show the level, email, Slack and Telegram text name it when the notification sets --min-severity or --severity, and a webhook body carries severity.
--active-hours SPEC sends opened notices only inside weekly windows, read in --timezone ZONE, which it requires. SPEC is DAYS HH:MM-HH:MM, such as "Mon-Fri 08:00-22:00"; repeat it up to 16 times. With --outside-hours hold, the default, a notice that falls due outside the hours waits for the next window and goes out then if its condition still holds, so a condition that clears first sends nothing. With drop the notice is skipped as outside_hours, which notifications deliveries lists. A resolved notice for an opened notice that was sent always goes out, crashing follows the hours like any other condition, a snooze is checked first, and notifications test ignores hours, severity and audience.
--audience FILE (inline JSON, a file, or - for stdin) chooses the screens with the rules of a campaign audience, such as {"all":[{"tag":"level2"},{"tag":"overhead"}]} for screens carrying both tags; --all-tags TAG and --any-tag TAG are the shorthands for an all and an any rule of tags, and --tag means any of the tags. --tag, --any-tag, --all-tags and --audience exclude one another.
loop_stalled, glass_blank and glass_frozen are verdicts screenRIG draws from the play reports and screenshots it receives, and they leave out what they cannot judge: a screen that is offline, whose display is off, that is under a takeover or suspended, that shows a single still or a video wall, or whose Player sends no play numbers. When screenRIG stops judging a screen it had found in one of them, the alert closes, but email, Slack, Teams and Telegram send no resolved notice, because nothing showed the screen recovering; PagerDuty still resolves its incident.
source_failed and source_stale watch the project's data sources, not its screens: a notice names the source and links its page, and --tag never matches a source.
card_data_stale watches a card, not a screen. Write a card's data with an expiry sized to its writer's schedule (card data put --ttl 30m), and when the job or agent that renews it stops, the data expires, the card is blank on every screen that shows it, and this condition tells you. The notice names the card and when its data expired, and links the page editor on a page that places it. A data source's documents are never announced here (source_stale covers them), and neither is a card no screen shows, data written with no expiry, or data that was deleted. --tag never matches a card.
reach_dropped and group_empty watch a campaign, not a screen. The notice names the campaign and the screens it reached before and now, or the empty tag or folder, and links to the campaign's calendar. A campaign belongs to the project, so --tag, --any-tag, --all-tags and --audience never limit these two; --min-severity, --severity, --active-hours and --hold apply as for any condition, and both are warning by default. --reach-drop-screens N and --reach-drop-percent P send a reach_dropped notice only for a drop of at least N screens and at least P percent of the screens the campaign reached when it was published; a drop must meet both, and without them any drop notifies. They need reach_dropped in --conditions. Each empty tag or folder of a campaign is its own notice, and notifications deliveries shows it as audience_group (tag:Lobby or folder:sfd_…). A publish that removes an empty group from the audience closes a PagerDuty incident and a webhook's alert but sends no recovery email or chat message.
Email goes to each recipient's verified address, one email per recipient for everything that falls due within about two minutes, with a link to each screen and to the notification in the dashboard. Recipients are owner, whoever owns the project when the notice sends, and the user ids of project members. A recipient with no verified address, or who has left the project, is skipped. Notification emails and screen notices are free.
Every email carries an unsubscribe link for its own recipient: the Unsubscribe button of mail apps, and a link in the footer. Following it stops that notification's emails to that one person, without signing in, and the page it opens has a Resubscribe button. The other recipients are unaffected, and the person stays in the recipients. notifications show lists who unsubscribed with unsubscribed, and notifications resubscribe mails them again. A notice with nobody left to email is skipped as no_recipients.
A webhook notification POSTs each notice as JSON, which you verify as described under Verify a delivery. With --events it also POSTs raw project events as they happen; those deliveries are metered as described under Webhooks.
A Slack, Teams, PagerDuty or Telegram notification posts one message per outage and screen, or per group of screens: the screen, what changed, since when, the project, a link to the screen, and, when an offline screen is back, its likely cause if one is known by then. A recovery message follows once the first one went out, and in PagerDuty it resolves the incident the first one opened. You give the credential once (--slack takes an incoming webhook URL, --teams the URL of a Workflows webhook, --pagerduty an Events API v2 routing key, --telegram a bot token with --telegram-chat); it is sealed, never shown again, and destination_hint shows only its masked end. A - reads it from one line of stdin so it stays out of your shell history. A failed post is retried for 24 hours, and a notification that fails for 72 hours is disabled.
When three or more screens fall into the same condition within five minutes of one another and share a network, location, model, Player version or tag, an email, Slack, Teams, PagerDuty or Telegram notification sends one notice for them instead of one per screen, and one notice says the group when its last screen recovers; each other screen's notice is skipped as grouped. A webhook still sends one notice per screen.
Reading notifications and their deliveries needs Read only access; creating, changing, testing and deleting one, rotating its secret and resubscribing a person need Manage access.
| Problem code | Exit | Meaning |
|---|---|---|
webhook_url_rejected | 8 | The URL is not https on port 443 or 8443, or its host does not resolve only to public Internet addresses. error.detail carries the server's reason. Nothing was created or changed. |
notification_destination_rejected | 8 | The destination does not fit its channel, or is a retired Office 365 connector URL. error.detail names the shape that fits. Nothing was created or changed. |
notification_limit_reached | 5 | The organization already has 50 notifications. Delete one first. |
revision_conflict | 6 | The --expect-rev revision was stale. Run notifications show, then retry. |
rate_limited | 7 | More than 20 notifications test calls in a minute for this organization. |
invalid_request | 8 | An --email recipient who is not a member of the project, or an option that belongs to another channel, such as --url on an email notification. Nothing was created or changed. |
notifications create#
Create an email, webhook, Slack, Teams, PagerDuty or Telegram notification.
notifications create (--email RECIPIENTS | --webhook URL | --slack URL | --teams URL | --pagerduty KEY
| --telegram TOKEN --telegram-chat CHAT_ID) [--conditions LIST] [--events TYPES]
[--tag TAG... | --any-tag TAG... | --all-tags TAG... | --audience FILE] [--hold MINUTES]
[--min-severity LEVEL] [--severity CONDITION=LEVEL]... [--active-hours SPEC]...
[--timezone ZONE] [--outside-hours hold|drop] [--reach-drop-screens N] [--reach-drop-percent P]
[--description TEXT] [--disabled]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--email RECIPIENTS | string | one channel flag | none | Send email to these comma-separated recipients: owner, or the user id of a project member, at most 20. |
--webhook URL | string | one channel flag | none | An https URL on port 443 (the default) or 8443 whose host resolves only to public Internet addresses. The server checks it on every write and again before every delivery. A URL carrying credentials (user:pass@ or token@) or a # fragment is refused locally with exit 2. |
--conditions LIST | string | yes, except a webhook | none | Comma-separated conditions from the table above. A webhook needs this, --events, or both. |
--slack URL | string | one channel flag | none | Post to Slack through an incoming webhook URL on hooks.slack.com, or - to read it from one line of stdin. |
--teams URL | string | one channel flag | none | Post to Teams through the URL of a Workflows webhook (the trigger “When a Teams webhook request is received”), or - for stdin. Office 365 connector URLs are retired and refused. |
--pagerduty KEY | string | one channel flag | none | Trigger and resolve PagerDuty incidents with an Events API v2 routing key of 32 characters, or - for stdin. |
--telegram TOKEN | string | one channel flag | none | Post to Telegram with a bot token, or - for stdin. It needs --telegram-chat. |
--telegram-chat CHAT_ID | string | with --telegram | none | The chat to post to: its numeric id, such as -1001234567890, or @channelname. |
--events TYPES | string | no | none | Webhook only. Also POST these raw project events: 1 to 32 comma-separated exact types, such as screen.online, or prefixes ending in .*, such as screen.*. |
--tag TAG | string | no | every screen | Only screens carrying any of these tags. Repeat the flag or comma-separate, up to 16 tags. |
--hold MINUTES | integer | no | 10 | How long a condition must last before it notifies, 0 to 1440. crashing is never held. |
--all-tags TAG | string | no | none | Only screens carrying every listed tag. Repeat the flag or comma-separate. |
--any-tag TAG | string | no | none | Only screens carrying at least one listed tag, as --tag. Repeat the flag or comma-separate. |
--audience FILE | string | no | every screen | The screens as a campaign audience rule in JSON: inline, a file, or - for stdin. Not with the tag options. |
--min-severity LEVEL | string | no | none | info, warning or error: opened notices below it are not sent. |
--severity CONDITION=LEVEL | string | no | the defaults | Sets one condition's severity for this notification. Repeat the flag. |
--active-hours SPEC | string | no | always | Send opened notices only inside this weekly window, DAYS HH:MM-HH:MM such as "Mon-Fri 08:00-22:00". Repeat up to 16 times. Needs --timezone. |
--timezone ZONE | string | with --active-hours | none | The IANA zone the hours are read in, such as America/Los_Angeles. |
--outside-hours MODE | string | no | hold | hold waits for the next window; drop skips the notice. |
--reach-drop-screens N | integer | no | any drop | Only with reach_dropped: send a notice only when the campaign lost at least N screens (1 or more). |
--reach-drop-percent P | integer | no | any drop | Only with reach_dropped: also require a loss of at least P percent (1 to 100) of the screens the campaign reached when it was published. |
--description TEXT | string | no | none | At most 200 characters. |
--disabled | boolean | no | off | Create it disabled. Nothing is sent until notifications update --enable. |
screenrig notifications create --email owner --conditions screen_offline,display_disconnected --tag Lobbyscreenrig notifications create --webhook https://hooks.example.com/screenrig --conditions screen_offline,crashingscreenrig notifications create --slack - --conditions screen_offline,crashing < slack-url.txtThe envelope is the notification. A webhook's carries its signing secret in data.secret (whsec_…) with the warning webhook_secret_shown_once. Store the secret in the receiver's secret store right away. Only notifications rotate-secret returns it again, and the CLI never writes it to its configuration, operation log, or write-recovery state. 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 answer, secret included. Notices start with the first event after the notification is created.
notifications list#
List this project's notifications, never their secrets.
notifications listscreenrig notifications listEach row is a notification of any channel, oldest first, with its status. A Slack, Teams, PagerDuty or Telegram notification shows destination_hint, its credential masked to the host and last characters, in the TO column of --human. Below Manage access a webhook's url is empty and url_redacted is true, and TO shows (withheld).
notifications show#
Inspect a notification and its delivery health.
notifications show <id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The ntf_ (any channel but webhook) or whk_ (webhook) notification id. |
screenrig notifications show ntf_01EXAMPLENOTICE00000status is active, failing while no delivery has succeeded since failing_since, or disabled. After 72 hours of continuous failure the notification is disabled with disabled_reason: delivery_failures, and the event log records notification.disabled. snoozed_until is present while a snooze is set. last_success_at and last_failure_at report the latest outcomes. revision is the value --expect-rev guards. min_severity, severity, active_hours with outside, audience and reach_drop appear while set, and an email notification lists unsubscribed.
notifications update#
Change a notification's recipients, URL, destination, conditions, screens, hold, snooze, or enabled state.
notifications update <id> [--email RECIPIENTS | --url URL | --destination VALUE] [--telegram-chat CHAT_ID]
[--conditions LIST | --clear-conditions]
[--events TYPES | --clear-events] [--tag TAG... | --any-tag TAG... | --all-tags TAG... | --audience FILE | --all-screens]
[--hold MINUTES] [--min-severity LEVEL | --clear-min-severity]
[--severity CONDITION=LEVEL]... [--clear-severity]
[--active-hours SPEC]... [--timezone ZONE] [--outside-hours hold|drop] [--clear-active-hours]
[--reach-drop-screens N] [--reach-drop-percent P] [--clear-reach-drop]
[--description TEXT | --clear-description] [--enable | --disable]
[--snooze WHEN] [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The ntf_ (any channel but webhook) or whk_ (webhook) notification id. |
--email RECIPIENTS | string | at least one change | unchanged | Email notifications: replaces the recipients. |
--url URL | string | at least one change | unchanged | Webhook notifications: replaces the URL, checked like create. |
--destination VALUE | string | at least one change | unchanged | Slack, Teams, PagerDuty and Telegram notifications: replaces the credential, checked like create. - reads it from one line of stdin. |
--telegram-chat CHAT_ID | string | at least one change | unchanged | Telegram notifications: replaces the chat. |
--conditions LIST | string | at least one change | unchanged | Replaces the conditions. |
--clear-conditions | boolean | at least one change | off | Remove every condition. A webhook then needs --events. |
--events TYPES | string | at least one change | unchanged | Webhook notifications: replaces the raw project events. |
--clear-events | boolean | at least one change | off | Webhook notifications: stop POSTing raw project events. It then needs conditions. |
--tag TAG | string | at least one change | unchanged | Replaces the screen tags, and an audience if the notification has one. |
--all-screens | boolean | at least one change | off | Remove the screen tags and the audience; the notification covers every screen. |
--hold MINUTES | integer | at least one change | unchanged | 0 to 1440. |
--all-tags TAG | string | at least one change | unchanged | Replaces the screens with those carrying every listed tag. |
--any-tag TAG | string | at least one change | unchanged | Replaces the screens with those carrying at least one listed tag, as --tag. |
--audience FILE | string | at least one change | unchanged | Replaces the screens with a campaign audience rule in JSON: inline, a file, or - for stdin. |
--min-severity LEVEL | string | at least one change | unchanged | info, warning or error. |
--clear-min-severity | boolean | at least one change | off | Remove the severity filter. |
--severity CONDITION=LEVEL | string | at least one change | unchanged | Replaces the severity overrides with these. Repeat the flag. |
--clear-severity | boolean | at least one change | off | Remove every severity override. |
--active-hours SPEC | string | at least one change | unchanged | Replaces the windows with these. Repeat up to 16 times. --timezone defaults to the zone the notification already has. |
--timezone ZONE | string | at least one change | unchanged | The IANA zone the hours are read in. Alone, it keeps the current windows. |
--outside-hours MODE | string | at least one change | unchanged | hold or drop. Alone, it keeps the current windows. |
--clear-active-hours | boolean | at least one change | off | Remove the active hours. Notices that were held for them are judged again at once. |
--reach-drop-screens N | integer | at least one change | unchanged | Replaces the least number of screens a reach_dropped notice needs the campaign to have lost. |
--reach-drop-percent P | integer | at least one change | unchanged | Replaces the least percentage of its published reach the campaign must have lost. |
--clear-reach-drop | boolean | at least one change | off | Remove the threshold, so any drop notifies. Take reach_dropped out of --conditions only together with it. |
--description TEXT | string | at least one change | unchanged | At most 200 characters. |
--clear-description | boolean | at least one change | off | Remove the description. |
--enable | boolean | at least one change | off | Clears the failure state and starts at the event that records the change; nothing from while it was off is sent. |
--disable | boolean | at least one change | off | Stops notices; pending notices fail with webhook_disabled. |
--snooze WHEN | string | at least one change | unchanged | Send nothing until then: a duration such as 30m, 1h or 2d (under 30 days), an RFC 3339 instant with seconds and an offset, or off to end the snooze. Notices that fall due meanwhile are skipped, not sent later. |
--expect-rev REVISION | string | no | none | Optional revision precondition. |
screenrig notifications update ntf_01EXAMPLENOTICE00000 --snooze 2hupdate changes only the fields you pass. The channel never changes: --email applies to an email notification, --url and --events to a webhook, and --destination and --telegram-chat to a chat or pager one, and an option of another channel is invalid_request (exit 8). Give at most one of --email, --url and --destination. The screen tags and an audience exclude each other: setting one clears the other. An email notification keeps at least one condition, and a webhook keeps conditions or events. The signing secret is unchanged; use notifications rotate-secret.
notifications delete#
Delete a notification. Its pending notices fail with webhook_deleted.
notifications delete <id> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The ntf_ (any channel but webhook) or whk_ (webhook) notification id. |
--expect-rev REVISION | string | no | none | Optional revision precondition. |
screenrig notifications delete ntf_01EXAMPLENOTICE00000Its delivery log cannot be read afterwards; its rows expire with event retention.
notifications rotate-secret#
Replace a webhook notification's signing secret and receive the new one once. Any other channel has no secret and answers invalid_request (exit 8).
notifications rotate-secret <id> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The ntf_ (any channel but webhook) or whk_ (webhook) notification id. |
--expect-rev REVISION | string | no | none | Optional revision precondition. |
screenrig notifications rotate-secret whk_01EXAMPLEWEBHOOK00000The envelope carries the new secret in data.secret with the warning webhook_secret_shown_once; store it right away. The previous secret stops signing, but at most one attempt already in flight can still arrive signed with it, so accept both for a short while. After an interrupted or ambiguous rotation, rerun the identical command within 24 hours to get the same secret back.
notifications test#
Send one test email, signed test POST, or chat message to this notification only.
notifications test <id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The ntf_ (any channel but webhook) or whk_ (webhook) notification id. |
screenrig notifications test ntf_01EXAMPLENOTICE00000The envelope is the queued delivery. It is attempted once without retries, works on a disabled or snoozed notification, and never changes the notification's failure state. At most 20 test deliveries a minute per organization; more is rate_limited (exit 7). Read the outcome with notifications deliveries.
A PagerDuty test triggers an information event keyed screenrig:test:DELIVERY_ID and resolves it at once, so no incident stays open.
notifications resubscribe#
Email a person again who unsubscribed from a notification's emails.
notifications resubscribe <id> --user USER_ID| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The ntf_ id of an email notification. |
--user USER_ID | string | yes | none | The person's user id, as notifications show lists it under unsubscribed. |
screenrig notifications resubscribe ntf_01EXAMPLENOTICE00000 --user usr_01EXAMPLEPERSON0000The envelope is the notification. A person who is not unsubscribed changes nothing. Resubscribing a person raises the revision and appends notification.updated. A notification of another channel or project is not_found (exit 4).
notifications deliveries#
Read a notification's delivery log, newest first.
notifications deliveries <id> [--before CURSOR] [--limit N]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The ntf_ (any channel but webhook) or whk_ (webhook) notification id. |
--before CURSOR | string | no | the newest event | The next_cursor (whc1_… or whc2_…) of the previous page. |
--limit N | integer | no | 50 | Rows per page, 1 to 200. |
screenrig notifications deliveries ntf_01EXAMPLENOTICE00000 --limit 20Each row is one event, or one notice of one condition on one screen: event_type, state (pending, succeeded, failed, or skipped), attempts, last_status, and a fixed last_error class such as http_status, timeout, connection_failed, tls_failed, dns_failed, url_rejected, email_failed, email_rejected, or secret_unavailable. A screen notice adds condition, phase (opened or resolved), screen_id, screen_name (for a data source, a campaign or a card, its id and name) and since, and an email notice adds emailed, the number of people reached. A notice that never needed sending is skipped, with its reason in last_error: condition_cleared (the screen left the state before the hold ended), not_opened (no notice went out to resolve), snoozed (the notification is snoozed), acknowledged (someone acknowledged or snoozed the screen's alert during the hold), muted (a mute covers the screen), outside_hours (the notification's active hours were closed and it drops notices outside them), below_severity (the notice is below min_severity, which was raised after it was queued), planned_reboot (the screen went offline, disconnected its display or crashed while restarting for a reboot screenRIG sent, until it is done or ten minutes after it was sent), no_recipients (no recipient is a project member with a verified address who has not unsubscribed), grouped (the screen fell into the condition with others, and one notice on another row says them all), or no_longer_judged (a loop_stalled, glass_blank or glass_frozen condition ended because screenRIG stopped judging the screen, a reach_dropped one because the campaign was published again or deleted, a group_empty one because a publish removed the tag or folder from its audience, or a card_data_stale one because the card's data was deleted, so no recovery was said). The notice that said a group of screens carries group (id, attribute, value and count), and each other screen of the group carries group_id; --human shows both in a GROUP column. Rows never include the receiver's response. next_cursor is null at the end of the log. A failed attempt is retried with backoff, 30 seconds doubling to one hour, for 24 hours, then the row is failed.
Verify a delivery
A screen notice's body is JSON: type (alert.opened or alert.resolved), id (the delivery id), notification_id, project_id, condition, screen (id and name), since (when the condition began), at (when it held long enough, or cleared), event_id and severity (info, warning or error: the condition's level on alert.opened, info on alert.resolved). alert.resolved for screen_offline also carries likely_cause (device_restart, app_crash, app_restart, network or platform), null while the cause is unknown. For a data source, a campaign or a card, screen names it, and a campaign notice adds reach (published and current screens) for reach_dropped or audience_group (kind tag or folder, and value) for group_empty. A raw event from --events is the same JSON object events list returns. The headers are ScreenRig-Webhook-Id (the notification id), ScreenRig-Event-Id (stable across retries: the delivery id for a notice, the event cursor for an event), and ScreenRig-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 over "<t>.<raw body>" keyed with the secret string. Compute it over the raw bytes before parsing JSON, compare in constant time, reject an old t, and deduplicate on ScreenRig-Event-Id, because delivery is at least once and unordered. Answer 2xx within 10 seconds; redirects are not followed.
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the request body bytes (Buffer) exactly as received.
function verify(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
if (typeof signatureHeader !== "string" || !signatureHeader) return false;
const parts = Object.fromEntries(signatureHeader.split(",").map((part) => part.split("=", 2)));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest();
const given = Buffer.from(parts.v1 ?? "", "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}Alerts
An alert is one problem on one screen while it lasts. It opens when a screen enters one of the screen notification conditions (screen_offline, display_disconnected, running_hot, on_battery, storage_full, content_stuck, not_playing, loop_stalled, glass_blank or glass_frozen), whether or not a notification watches it, and resolves when the condition ends. A screen has at most one open alert per condition. A crash spike, a data source, a card and a campaign are notices, never alerts. A screen that restarts for a reboot screenRIG sent (screen reboot or a reboot schedule) opens no screen_offline or display_disconnected alert until the reboot is done or ten minutes after it was sent; a condition that still holds then opens its alert. alerts list shows what is open now.
An open alert needs attention until someone acknowledges or snoozes it, or a mute covers its screen. Acknowledging says someone is on it: the alert stays open, records who acknowledged it and when, and needs no attention until it resolves. Snoozing acknowledges until a time at most 30 days ahead, and the alert needs attention again then if it is still open. A later acknowledge or snooze replaces the earlier one. While an alert is acknowledged or snoozed, a notification's notice about it that is still in its hold is skipped.
A mute is for maintenance: it names screens, tags or both and ends at a time at most 30 days ahead. It covers a screen while the screen's id is listed or it carries a listed tag, so a screen tagged later is covered and one that loses the tag is not. A muted screen's alerts still open and resolve, marked muted, but never need attention, and every notification skips their notices. An organization has at most 50 current mutes across its projects.
counts.attention in alerts list is the number the dashboard shows beside Alerts. Opening, resolving and acknowledging an alert append screen.alert_opened, screen.alert_resolved and screen.alert_acknowledged to the project event log, and creating and ending a mute append alert_mute.created and alert_mute.deleted. They are free on the listen stream and on webhooks, as every screen.* event is. Resolved alerts stay listed for the plan's event retention. Reading alerts and mutes needs Read only access; acknowledging, snoozing, muting and unmuting need Manage access.
| Problem code | Exit | Meaning |
|---|---|---|
alert_not_open | 5 | The alert already resolved, so there is nothing to acknowledge or snooze. Run alerts list and use an open one. |
alert_mute_limit_reached | 5 | The organization already has 50 current mutes. End one with alerts unmute, or cover more screens with one mute by tag. |
invalid_request | 8 | The --until instant is more than 30 days ahead. Nothing was changed. |
not_found | 4 | No alert or mute has this id in the project. A resolved alert is gone after the plan's event retention, and a mute once it has ended. |
alerts list#
List this project's alerts, open ones first.
alerts list [--state open|resolved|all] [--screen-id ID] [--before CURSOR] [--limit N]
[--format json|csv|xlsx|pdf] [--output FILE]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--state STATE | string | no | open | open, resolved or all. |
--screen-id ID | string | no | every screen | Only this screen's alerts. |
--before CURSOR | string | no | the first page | The next_cursor of the previous page. |
--limit N | integer | no | 100 | Rows per page, 1 to 200. |
--format FORMAT | string | no | json | json, or csv, xlsx or pdf to write the alerts as a file instead of printing them. |
--output FILE | path | no | ./alerts.FORMAT | The file to write with --format csv, xlsx or pdf. Not with json. |
screenrig alerts listscreenrig alerts list --state all --screen-id scr_01EXAMPLESCREEN00000000screenrig alerts list --state all --format csv --output alerts.csvEach row is one alert: id (alr_…), screen_id, screen_name, condition, state (open or resolved), since (when the condition began), resolved_at once it resolved, acknowledged_at and acknowledged_by (kind, id and name of the person or agent) once acknowledged, snoozed_until while snoozed, muted and attention. Open alerts come first, newest first. counts carries open and attention for the whole project, whatever the page and filters, and next_cursor is null at the end.
With --format csv, xlsx or pdf the file holds every alert the filters match, up to 50,000 rows, and the envelope carries its path, bytes and sha256. Reports and exports describes the files.
alerts show#
Show one alert.
alerts show <id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The alr_ alert id. |
screenrig alerts show alr_01EXAMPLEALERT000000The envelope is the alert, in the shape of an alerts list row.
alerts ack#
Acknowledge an open alert: someone is on it.
alerts ack <id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The alr_ alert id. |
screenrig alerts ack alr_01EXAMPLEALERT000000The alert stays open and needs no attention until it resolves; acknowledged_by names the person or agent and acknowledged_at the time. A notification's notice still in its hold is skipped. Acknowledging again replaces the earlier acknowledge or snooze. A resolved alert is alert_not_open (exit 5). The envelope is the alert, and the event log records screen.alert_acknowledged.
alerts snooze#
Acknowledge an open alert until a time, so it needs attention again then.
alerts snooze <id> (--for DURATION | --until TIME)| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The alr_ alert id. |
--for DURATION | duration | one of --for, --until | none | How long to snooze from now: a duration such as 30m, 4h or 2d, at most 29d23h59m. |
--until TIME | string | one of --for, --until | none | When the snooze ends: an RFC 3339 instant with seconds and an offset, such as 2026-11-01T09:00:00-07:00, at most 30 days ahead. |
screenrig alerts snooze alr_01EXAMPLEALERT000000 --for 4hsnoozed_until is the time the snooze ends. Until then the alert needs no attention and a notification's notice still in its hold is skipped, as for alerts ack; if the alert is still open at that time, it needs attention again. A resolved alert is alert_not_open (exit 5).
alerts mute#
Mute screens for maintenance.
alerts mute [--screen-id ID]... [--tag TAG]... (--for DURATION | --until TIME) [--note TEXT]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--screen-id ID | string | at least one of --screen-id, --tag | none | A scr_ screen id. Repeat it, or list ids comma-separated; at most 100. |
--tag TAG | string | at least one of --screen-id, --tag | none | Every screen carrying this tag. Repeat it, or list tags comma-separated; at most 16. |
--for DURATION | duration | one of --for, --until | none | How long the mute lasts from now: a duration such as 30m, 4h or 2d, at most 29d23h59m. |
--until TIME | string | one of --for, --until | none | When the mute ends: an RFC 3339 instant with seconds and an offset, such as 2026-11-01T09:00:00-07:00, at most 30 days ahead. |
--note TEXT | string | no | none | Why the screens are muted, for the people who see the mute; at most 200 characters. |
screenrig alerts mute --tag Lobby --for 2h --note "Rewiring the lobby displays"The envelope is the mute: id (amt_…), screen_ids, tags, until, note, created_by and created_at. The alerts of every covered screen read muted at once, those already open included. An organization has at most 50 current mutes across its projects; one more is alert_mute_limit_reached (exit 5), and a mute that has ended no longer counts. After an interrupted or ambiguous mute, read alerts mutes first: rerunning the identical command with --until within 24 hours returns the same mute, while a rerun with --for counts from a new time and makes another.
alerts mutes#
List the mutes that have not ended, soonest end first.
alerts mutesscreenrig alerts mutesEach row is a mute as alerts mute prints it.
alerts unmute#
End a mute now.
alerts unmute <mute-id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<mute-id> | string | yes | none | The amt_ mute id. |
screenrig alerts unmute amt_01EXAMPLEMUTE0000000The screens' open alerts need attention again, and later notices are no longer skipped as muted. A mute that has ended is not_found (exit 4). The event log records alert_mute.deleted.
Webhooks
A webhook sends this project's own events to an HTTPS endpoint you run as signed POSTs, so a matching event can wake an agent, a bot, or your own service. Each matching event arrives as the same JSON object events list returns. A webhook is a notification in its original form: it counts toward the organization's 50 notifications, and notifications list shows it. New work uses notifications, which also sends email and screen notices.
Each delivery attempt is free and does not count toward the organization's hourly agent request cap.
webhooks create --url URL --event-types TYPES [--description TEXT] [--disabled]
webhooks list
webhooks show <id>
webhooks update <id> [--url URL] [--event-types TYPES] [--description TEXT | --clear-description]
[--enable | --disable] [--expect-rev REVISION]
webhooks delete <id> [--expect-rev REVISION]
webhooks rotate-secret <id> [--expect-rev REVISION]
webhooks test <id>
webhooks deliveries <id> [--before CURSOR] [--limit N]webhooks create answers webhook_limit_reached (exit 5) when the organization already has 50 notifications, where notifications create answers notification_limit_reached. A refused URL is webhook_url_rejected (exit 8) for both.
--url takes an https URL on port 443 or 8443 whose host resolves only to public Internet addresses, checked like notifications create; a URL that carries credentials or a # fragment is refused locally with exit 2. --event-types takes 1 to 32 comma-separated exact types, such as screen.online, or prefixes ending in .*, such as screen.*. --description is at most 200 characters. --disabled creates the webhook disabled. webhooks update --enable clears the failure state and starts at the current event, so nothing from while it was disabled is sent, and --disable fails pending deliveries with webhook_disabled. --expect-rev guards the revision (revision_conflict, exit 6).
webhooks create and webhooks rotate-secret print the signing secret once in data.secret with the warning webhook_secret_shown_once; after an interrupted or ambiguous call, rerun the identical command within 24 hours to get the same answer back. Delivery starts with the first event after the webhook is created. webhooks test queues one webhook.test delivery for this webhook only; it works on a disabled webhook, is attempted once without retries, is metered like any delivery, and is limited to 20 a minute per organization (rate_limited, exit 7). webhooks list shows this project's webhook notifications, oldest first, and a Read only credential receives each url empty with url_redacted true. webhooks deliveries reads the log newest event first, 1 to 200 rows a page (default 50), with next_cursor (whc1_…) as --before; a row's state is pending, succeeded or failed, with last_status and a fixed last_error class. Verify a delivery as described under Verify a delivery.