Every command runs the same way
screenrig [--json | --human] [--api-url URL] [--config PATH] [--request-id ID]
[--project-id ID] [--idempotency-key KEY] [--timeout MS] [--beta-key KEY] <command> [args]Global flags
| Flag | Type | Default | Notes |
|---|---|---|---|
--json | boolean | JSON for operations | Explicit JSON output, retained for compatibility. Also selects structured help. Cannot be combined with --human. |
--human | boolean | off | Select human-readable operational output. Cannot be combined with --json. |
--project-id ID | string | last-used project | Fix this invocation to a cached project without changing the selection. Each command keeps its target and recovery state for its full run. |
--api-url URL | string | https://api.screenrig.ai | Control-plane origin. SCREENRIG_API_URL sets the same thing. |
--config PATH | path | the user configuration file | Use a specific configuration file. SCREENRIG_CONFIG sets the same thing. |
--request-id ID | string | generated per run | Your own request id, echoed back as request_id. Keep it in reports. |
--idempotency-key KEY | string | derived per write | Reuse the same key to retry an ambiguous write. The server returns the original result for twenty-four hours; a different body under the same key is idempotency_mismatch. |
--timeout MS | integer | 30000 per request | Bounds each HTTP request. On a command that waits for an operation it is the wait deadline instead, and the default is 120000. |
--beta-key KEY | string | unset | Sent only when the control plane asks for one. SCREENRIG_BETA_KEY sets the same thing. |
--help, -h | boolean | off | Print focused help for the command or group and exit. Add --json for structured help. |
--version, -V | boolean | off | Print the bundled CLI version and exit. Same as the version command. |
Environment
| Variable | Effect |
|---|---|
SCREENRIG_API_URL | Control-plane origin, overriding the stored configuration. |
SCREENRIG_CONFIG | Path to the configuration file. |
XDG_CONFIG_HOME | Base of the configuration directory. Otherwise %APPDATA%\screenrig on Windows, or ~/.config/screenrig. |
SCREENRIG_BETA_KEY | Beta key, when the control plane asks for one. |
SCREENRIG_FFMPEG | Absolute path to ffmpeg, when media upload should not take it from PATH. |
SCREENRIG_FFPROBE | Absolute path to ffprobe. |
SCREENRIG_CWEBP | Absolute path to cwebp, the fallback still encoder. |
SCREENRIG_CLIENT_ID | Run as this service client (scl_…), with exactly one of the next two. |
SCREENRIG_CLIENT_KEY_FILE | The service client's private key file, a JWK or PEM, signed into a private_key_jwt assertion. |
SCREENRIG_CLIENT_SECRET | The service client's secret, sent with HTTP Basic. |
There is no token flag and no token environment variable. The CLI reads the session the plugin stored for this installation, in a user-private file that survives a plugin upgrade, and renews it on its own. With SCREENRIG_CLIENT_ID set, it signs in as that service client instead, keeps the token in memory, and never reads or writes the stored config.
Output
Operational commands write JSON to stdout by default, including errors. A single result carries an envelope: ok, then data, request_id, operation_id, and warnings[] on success, or error on failure. Progress, safe URLs, and human notes go to stderr. events follow emits NDJSON: one JSON envelope per event, on its own line. Redirecting stdout does not change the format.
Use --human for manual inspection. Event commands then print one logfmt line per event. Help and bare command groups remain readable text by default; use screenrig --json playlist --help for structured discovery. --json and --human cannot be combined.
Older plugin bundles may require explicit --json for operational results. Check the installed CLI's help when working with an older bundle. Playlist and Compose authoring files remain JSON documents; a command result envelope is not an editable playlist document.
An error envelope carries error.status, a stable error.code, and often error.next.command naming exactly what to run next. Branch on those rather than on prose.
Exit codes
| Code | Meaning |
|---|---|
0 | Success. |
1 | Unexpected failure. |
2 | Usage error. Nothing was sent. |
3 | Credential problem. This installation cannot authenticate. |
4 | Not found. |
5 | Conflict. |
6 | Precondition failed, including revision_conflict: the --expect-rev revision was stale. |
7 | Rate limited. Honor Retry-After, surfaced as retry_after_seconds. |
8 | Other client-side rejection. |
9 | Server error. |
10 | Network failure. |
11 | Timed out. |
12 | Configuration error. |
13 | The operation itself failed. |
Common problem codes in the envelope:
| Code | Meaning | Next step |
|---|---|---|
not_enrolled | This installation holds no project credential yet. | Create a project with screenrig agent enroll --email ADDRESS --organization NAME, or sign this installation in to a project you belong to with screenrig login. |
session_ended | The sign-in was revoked, expired or ended. | screenrig login, approved by a person in the dashboard. |
insufficient_access | A Read only session tried to change, publish or spend. | screenrig login --access manage, approved by a person. |
login_denied, login_expired | The person denied the sign-in, or its 10-minute code expired. | Run screenrig login again. |
client_auth_failed | A service client's key, secret or id was refused. | Check the SCREENRIG_CLIENT_* variables against screenrig service-client show ID. |
capability_required | The project lacks the capability this command needs, such as screens or advertiser. | screenrig project capabilities, then choose the matching --intent for a new project or change the project’s purpose. |
rate_limited | Too many requests in the current window. | Wait for retry_after_seconds, then retry. |
Revisions and retries
Revision guards are optional. Omit --expect-rev to write the current resource without a prior revision read. Supply it with a known revision to reject stale writes. On revision_conflict, refetch the resource, reapply the change, and retry with the returned revision. On an ambiguous transport failure, retry with the same --idempotency-key; the control plane returns the original result rather than writing twice.
Credits
The control plane meters one credit per billed authenticated command and one per billed project-stream event. Five control-plane commands do not debit it and keep working at zero: project show, agent status, screen reload, screen toast, and screen screenshot. Local Compose work is never billed. Opening events follow costs one credit as the subscribe, and replay after a reconnect is free. A fleet command, screen assign, screen reload, screen toast, or screen tag with several screen ids or --tag, is one billed action with no screen-count cap, including all continuations, so a fleet reload or toast is billed even though a single one is free. screen screenshot --tag follows all list pages, each billed, to resolve the tagged screens; the captures themselves are free. Each webhook delivery attempt is metered like a billed listen-stream event. Each JSON page of playback plays or playback list is one billed request, and a playback CSV export is one billed request for its whole range.
Bundled CLI surface
Commands are grouped the way the CLI groups them. Installation and first use are covered in Start; local still rendering is covered in Compose.
Command names and discovery
Use show to inspect a resource, list for collections, and kv get / kv set
for values. Use --name for display names, --screen-id, --app-id,
--playlist-id, and --release-id for resource selectors, --expect-rev for
revision guards, and --after for event cursors. The group is app; “application”
is the resource name. Comments keep comment ACTION screen|playlist ID.
Compatibility aliases remain: operations get, playlist get, --if-match,
K/V --application-id, screen pair/provision --label, playlist init --screen,
and events --cursor. Supply only one spelling of an option. Response field
names are unchanged.
Use screenrig help --all for the full inventory or screenrig playlist init
--help for focused help. Add --json for argument metadata, choices, defaults,
examples, aliases, and option relationships. exactlyOne, atLeastOne, and
together groups are checked before configuration or network access; requires
means the first option requires all remaining options. Read stdin only where
command help explicitly supports -.
Create a new project
screenrig agent enroll --email ADDRESS --project-name "Lobby"
agent enroll creates an identity, organization, Screens project and membership, and stores identity and project credentials privately. Reuse the organization name already provided, or use Screens without a choice prompt. --organization is required; --name separately names the agent.
Use the person's actual contact email. Enrollment emails a project-member
invitation; it does not disclose whether that address already has a login.
The person opens the invitation and creates a login with a passkey,
or signs in to add the project to their existing login.
Add --intent advertising for a project that buys ads rather than pairing
screens. --force replaces this installation's enrollment or pending connection
state when intentionally creating another project.
agent enroll --email ADDRESS --organization NAME [--name NAME]
[--intent signage|advertising] [--force]
Sign in with AgentID
An agent with an AgentID signs in at https://api.screenrig.ai/agentid/start,
receives a claim code, installs the official plugin, and runs
screenrig agent enroll --agentid-claim CODE --organization NAME.
The owner's verified email receives the dashboard invitation.
Sign in to an existing account
screenrig loginlogin signs this installation in to an account that already exists. It prints a dashboard address and a code, then waits up to 10 minutes. A person opens the address, checks that the code matches, chooses the project, Read only or Manage, and the capability areas, and approves. The CLI stores the session, selects the approved project and returns status: signed_in with the project, organization and access.
login [--project ID] [--access read|manage] [--name NAME] [--no-wait] [--resume ID]
logout--access asks for a level; the default is manage, and the person decides. --project suggests the project. --no-wait returns status: pending with the verification_uri_complete, the user_code and a login_ handle that holds no secret; login --resume ID waits for the approval later. Run login again on a signed-in installation to add another project or to raise a Read only project to Manage.
Each session carries capability areas: screens, content, playlists, advertising, reports, and project. A session calls only commands in its areas; reports also reads any area. A command outside them returns 403 forbidden with detail This agent credential lacks the <name> capability. Read only sessions list and read; a change returns 403 insufficient_access.
logout revokes the sign-in on the server and removes the stored tokens. A failure that might not have reached the server keeps them, so rerunning logout is safe. doctor shows when the session ends: 90 days after its last use, and at most one year after sign-in. See Authentication.
Project and organization lifecycle
The current project is the last one used. Every JSON envelope includes organization and project context; human output names both. Identity credentials are limited by the selected project's membership. Retry state stays separate per project.
project list
project use ID
project create NAME [--organization-id ID | --organization NAME]
project moves
project move --organization-id ID
project transfer-owner --user-id ID
project deletion-preview
project delete --name NAME --revision N --yes
organization list
organization rename ID NAME
agent revoke-identity --yesproject list verifies accessible memberships. project use selects one. Create requires a name unique in its organization and defaults to the current organization; specify an organization when there is no current project. The responsible person becomes owner. Ten free Standard projects per owner are allowed at creation, with enrollment governed by rate limits and covered projects allowed.
The owner can move into a destination returned by project moves, or transfer ownership to an existing verified member. A move carries screens and checks the destination payer's limit. Deletion uses the exact current name and positive revision from its preview. It requires zero non-archived screens, its own unshared Standard plan, no wallet obligations and no live advertising. It emails members and retains a tombstone.
Organization names can repeat; use explicit IDs when renaming an organization you administer. An organization has designated admins and no members. Its project list does not grant access to project content. Shared plan holders pool money, allowances and screen limits; usage remains attributed to projects and historical charges retain their original payer.
login --project ID asks for that project on the same identity. Only explicit approval of this request grants a membership. agent disconnect --yes revokes one membership; agent revoke-identity --yes separately revokes the identity and all memberships. An ambiguous response retains local credentials and retry state for reconciliation.
Advertising reports retain settlement facts beyond event retention. Read their coverage metadata: missing historical plays or unknown approved duration are unknown, never zero. A report can have complete financial totals and incomplete outcome history.
Projects, invitations, and diagnostics
Project commands list memberships, select a project, create projects, and manage their names, owners, moves and deletion. Invitations bring people into projects or advertising networks.
project show#
Read the project record and its remaining credit.
project show
No parameters.
screenrig project show
data.credit_remaining is a whole integer credit count. Below 1000 the envelope may carry a credits_low warning. Until 1 Jan 2027 usage is free within reason, so remaining credit is an integer meter an agent can read, not a shutoff instruction. Standard's free monthly allowances continue after that; extra usage is optional prepaid.
project rename#
Set the name people see in the dashboard project switcher.
project rename NAME
project capabilities#
Read the project's plan, independent screens and advertiser feature flags,
and effective capability set. A route denial wins over a stale capability read.
project capabilities
invitations#
Create, list, or revoke project-member and ad-buyer invitations.
invitations create --email ADDRESS[,ADDRESS] [--kind member|ad-buyer] [--link]
[--screen-id ID] [--slot-id ID] [--policy trusted|review_required]
invitations list [--kind member|ad-buyer] [--status STATUS]
invitations revoke ID
--kind defaults to member. Email delivery is the default and expires after
seven days. --email accepts comma-separated recipients, up to fifty per request.
--link is member-only, requires exactly one intended recipient, and returns a
twenty-four-hour invitation URL once for direct handoff. Never put that URL in
logs, notes, or a shared transcript. Lists never return it.
Ad buyers use email only and need an owned screen or slot scope (or both).
--screen-id and --slot-id accept comma-separated IDs; --policy selects
trusted or review_required. Status filters are queued, sent, issued,
accepted, revoked, expired, and failed.
screenrig invitations create --email teammate@example.com
screenrig invitations create --kind ad-buyer --email buyer@example.com --screen-id scr_LOBBY --policy review_required
Agents do not accept invitations. The invited person opens the email, signs in or creates a passkey, and accepts in the dashboard. Link signup verifies their email first. Revoking an accepted invitation does not undo a membership; use the buyer membership controls to revoke advertising access.
agent status#
Report whether this installation holds a usable credential.
agent status
No parameters.
screenrig agent status
Read-only. It never changes local or server state, and it costs nothing.
login#
Sign this installation in to an existing account, with a person's approval in the dashboard.
login [--project ID] [--access read|manage] [--name NAME] [--no-wait] [--resume ID]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--project ID | string | no | none | Suggest the project the person approves. |
--access LEVEL | string | no | manage | read or manage. The person makes the final choice. |
--name NAME | string | no | none | Name this installation, at most 80 characters. |
--no-wait | boolean | no | off | Return status: pending with the address, the code and a login_ handle. |
--resume ID | string | no | none | Wait for the pending sign-in that --no-wait started. |
screenrig login --access readPrints the dashboard address and an eight-letter code to stderr, then waits up to 10 minutes. The person checks that the code matches, chooses the project, the access level and the capabilities, and approves. The envelope reports status: signed_in, the project, the organization and access. Running it again on a signed-in installation adds a project or raises a Read only project to Manage. Denial is login_denied and expiry is login_expired.
logout#
End this installation's sign-in and remove its tokens.
logoutNo parameters.
screenrig logoutRevokes the sign-in on the server, then removes the stored tokens and reports status: signed_out. A failure that might not have reached the server keeps them, so rerunning is safe. Run screenrig login to sign in again.
dashboard#
Open the ordinary dashboard URL. This command does not mint a sign-in link, create a session, or need an agent credential.
dashboard [open]
screenrig dashboard
The dashboard shows the person's projects after sign-in. Browser project selection does not change the project bound to an agent.
dashboard reset-sign-in#
Request a sign-in reset email without an agent credential.
dashboard reset-sign-in --email ADDRESS
The acknowledgment does not reveal whether the email has a login. The emailed reset expires after one hour; the person creates a new passkey. Completion replaces their sign-in credentials and revokes other sessions.
doctor#
Check this installation and report every check by name.
doctor [--repair-config]
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--repair-config |
boolean | no | off | Tighten the permissions on an existing configuration file that is readable by group or others. |
screenrig doctor
data.checks names each check and its status: the Node runtime, the resolved configuration path and its permissions, the control-plane origin and its service routes, the advertised feedback capability, and the optional media toolchain (ffmpeg, ffprobe, encoder_libx264, encoder_libx265, encoder_libwebp, cwebp, filter_hdr_tonemap).
Any failed check makes doctor exit non-zero, so read the individual names before calling the installation unhealthy. A missing media toolchain affects media upload alone.
version#
Print the bundled CLI version and its protocol adapter.
version
No parameters.
screenrig version
Run this first in a session. A successful envelope proves the launcher resolved the bundled CLI. --version and -V do the same thing from anywhere on the command line.
Service clients
A service client is a server, script or CI job owned by the project, signing in with its own key or secret instead of a person's approval. Managing service clients needs the project capability and Manage; a service client never manages clients. To run commands as the client, set SCREENRIG_CLIENT_ID with SCREENRIG_CLIENT_KEY_FILE or SCREENRIG_CLIENT_SECRET. See Service clients.
service-client list#
List this project's service clients, live and revoked, with their keys, secret ids and usage.
service-client listNo parameters.
screenrig service-client listNever returns a secret.
service-client show#
Show one service client.
service-client show ID| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
ID | string | yes | none | The client id, scl_…. |
screenrig service-client show scl_IDLists each key's kid and each secret's css_ id for rotation.
service-client create#
Create a service client owned by this project.
service-client create --name NAME [--capability NAME]... [--access read|manage] [--key-file PATH] [--secret-file PATH]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--name NAME | string | yes | none | At most 80 characters. |
--capability NAME | string | no | all six | Repeatable: screens, content, playlists, advertising, reports, project, each within yours. |
--access LEVEL | string | no | read | read or manage. |
--key-file PATH | path | one of these | none | A JWK or PEM: Ed25519, EC P-256 or RSA of at least 2048 bits. Only the public half is sent. |
--secret-file PATH | path | one of these | none | Generate a secret and write it to this new 0600 file. It is shown once. |
screenrig service-client create --name "Nightly report" --access read --capability reports --key-file screenrig-client.pemNeeds the project capability and Manage. At most 50 live clients per project. A key without its own kid is known by its RFC 7638 thumbprint.
service-client add-key#
Register a second public key for a rotation without downtime.
service-client add-key ID --key-file PATH| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
ID | string | yes | none | The client id. |
--key-file PATH | path | yes | none | The new public key, a JWK or PEM. |
screenrig service-client add-key scl_ID --key-file new.pemA client holds at most two keys.
service-client add-secret#
Generate a second secret for a rotation without downtime.
service-client add-secret ID --secret-file PATH| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
ID | string | yes | none | The client id. |
--secret-file PATH | path | yes | none | A new 0600 file that receives the secret. |
screenrig service-client add-secret scl_ID --secret-file new-secretA client holds at most two secrets. The current one keeps working until you remove it.
service-client remove-key#
Remove a key; tokens issued with it stop working within seconds.
service-client remove-key ID --kid KID| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
ID | string | yes | none | The client id. |
--kid KID | string | yes | none | The key id from service-client show. |
screenrig service-client remove-key scl_ID --kid KIDA client keeps at least one key or secret.
service-client remove-secret#
Remove a secret; tokens issued with it stop working within seconds.
service-client remove-secret ID --secret-id css_ID| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
ID | string | yes | none | The client id. |
--secret-id ID | string | yes | none | The css_ secret id from service-client show. |
screenrig service-client remove-secret scl_ID --secret-id css_IDservice-client revoke#
Revoke a service client permanently.
service-client revoke ID --yes| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
ID | string | yes | none | The client id. |
--yes | boolean | yes | off | Confirm the permanent revocation. |
screenrig service-client revoke scl_ID --yesDeletes its keys and secrets; its tokens stop working within seconds. The record stays for audit.
Media
A media object is one still, one clip, or one MP3 audio track, stored as exactly one rendition. There is no per-device fallback, so whatever media upload or media generate stores is what every screen has to decode.
media generate#
Generate a presentable whole-page still into the project media store.
media generate --prompt TEXT [--aspect-ratio RATIO] [--quality low|medium|high] [--tag TAG]
[--no-progress]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--prompt TEXT | string | yes | none | 1 to 4000 characters. Put every fact and all copy in the prompt so the image model typesets it. |
--aspect-ratio RATIO | string | no | 16:9 | 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, or 2:3. |
--quality | string | no | medium | low, medium, or high. Changes how detailed the still is. Generate is billed per token: $10 / 1M text input, $16 / 1M image input, $60 / 1M image output. |
--tag TAG | string | no | none | Same 1 to 32 letter-or-digit tag as upload. |
--no-progress | boolean | no | off | Suppress the stderr started line. |
screenrig media generate --prompt "A 16:9 restaurant menu poster for North Lobby Cafe. Dark walnut, gold type. Title NORTH LOBBY CAFE. Items: Espresso $4, Cortado $4.50, House Blend $3. Footer Open 7am to 7pm. Typeset every line of copy in the image." --aspect-ratio 16:9 --quality high --tag LobbyMenuThe command stores a lossy WebP in the project media store and returns data.media_id (med_…). Do not re-upload the bytes. Never print pixels or the prompt. The authoring tree and prompt contract live on Compose.
media upload#
Convert a local file to delivery bytes, upload it, and wait until the object is ready.
media upload <file> [--content-type TYPE] [--tag TAG] [--no-wait] [--poll-ms MS]
[--timeout MS] [--no-transcode] [--codec h264|hevc] [--max-fps N]
[--max-edge PIXELS] [--webp-quality 1-100] [--no-progress]
[--preset signage-1080p30|signage-4k30] [--no-audio]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<file> | path | yes | none | The source still, clip, or audio track. The filename becomes the human handle in listings, playlists, and playback reporting, so name it for what it shows. |
--content-type TYPE | string | no | detected from the uploaded bytes | Declare the media type yourself instead of letting the CLI detect it. |
--tag TAG | string | no | none | One to 32 letters or digits, matching ^[A-Za-z0-9]{1,32}$. Hyphens are rejected. A tag is what makes media list --tag TAG a reliable filename-to-id map. |
--no-wait | boolean | no | off | Return as soon as the commit is accepted. The object is not ready yet; finish with operations wait. |
--poll-ms MS | integer | no | 1000 | Interval between operation polls while waiting. |
--timeout MS | integer | no | 120000 | Deadline for the wait. It also bounds each underlying request. |
--no-transcode | boolean | no | off | Upload the source bytes unchanged. ffmpeg, ffprobe, and cwebp are not run. Lossless WebP is still rejected. |
--codec | string | no | h264 | h264 or hevc; avc and h265 are accepted as aliases. Use hevc only when every screen that will play the clip is a native Player. |
--max-fps N | number | no | 30 | Frame-rate cap. Greater than 0 and at most 240. |
--max-edge PIXELS | integer | no | 3840 | Bound applied to each edge, from 16 to 3840. Aspect ratio is preserved and a smaller source is never upscaled. |
--webp-quality | integer | no | 90 | Still quality for the WebP encode, from 1 to 100. |
--preset | string | no | unset | signage-1080p30 or signage-4k30. Orientation-aware video size and 30 fps caps. --max-edge and --max-fps can tighten a preset but cannot enlarge it. Video only; cannot combine with --no-transcode. |
--no-audio | boolean | no | off | Omit the audio track from video delivery. Video only; cannot combine with --no-transcode. |
--no-progress | boolean | no | off | Emit no progress output. Progress always goes to stderr; stdout stays reserved for the one result envelope. |
screenrig media upload ./lobby-welcome-loop.mp4 --tag LobbyLoopVideo becomes an MP4: H.264 High profile level 4.2 by default, or H.265 with --codec hevc, in yuv420p tagged Rec. 709. An HDR source is tone mapped to Rec. 709. Audio, where the source has any, is AAC at 192 kbit/s, 48 kHz, stereo.
Stills become lossy WebP at quality 90, keeping alpha where the source has it. An animated source loops forever. A WebP that already fits the size bound is passed through rather than re-encoded.
Audio becomes an MP3 for a playlist soundtrack. An MP3 source uploads unchanged; WAV, AAC, M4A, OGG and FLAC sources are converted to a 192 kb/s MP3, which needs an ffmpeg build with libmp3lame. A track runs from 1 second to 4 hours. playlist init turns audio inputs into soundtrack tracks rather than pages. The ready object is an audio media object with a duration. It cannot be placed on a page; name it in the playlist audio soundtrack instead.
The ready object id is data.media_id. The same value is data.id and data.operation.result.media_id. That id is what a playlist selector takes.
A low-information filename such as video.mp4 adds an advisory generic_filename warning to the envelope. The upload still succeeds and the CLI never renames the file; pick the name before you run the command.
Conversion runs ffmpeg and ffprobe from PATH, or from the absolute paths in SCREENRIG_FFMPEG and SCREENRIG_FFPROBE. Stills prefer the ffmpeg libwebp encoder and fall back to cwebp on PATH or SCREENRIG_CWEBP. Run doctor before the first upload of a session.
--preset signage-1080p30 bounds the long edge to 1920, the short edge to 1080, and the rate to 30 fps. signage-4k30 bounds the long edge to 3840, the short edge to 2160, and the rate to 30 fps. Both work with landscape, portrait, and square content. --no-audio omits audio encoding for silent clips. These options apply only to video.
screenrig media upload ./lobby.mov --preset signage-1080p30 --no-audiomedia upload-batch#
Upload many local files from a manifest, resuming from a required state file.
media upload-batch <manifest.json> [--state FILE] [--concurrency N]
[--no-transcode] [--tag TAG] [--no-progress]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<manifest.json> | path | yes | none | { "items": [ { "path": "./a.png", "tag"?: "Lobby", "content_type"?: "image/png" } ] } with 1 to 1000 items. Paths are relative to the manifest file. |
--state FILE | path | no | private file beside config | A 0600 JSON file keyed by the SHA-256 of each source file's local bytes. Items already present with a media_id are reported as resumed. Do not share one state file across projects. |
--concurrency N | integer | no | the CLI default | Parallel uploads. |
--no-transcode | boolean | no | off | Same as media upload. |
--tag TAG | string | no | none | Applied when an item omits its own tag. |
--no-progress | boolean | no | off | Emit no progress output. |
screenrig media upload-batch ./images.json --state ./upload-state.jsonThe envelope carries data.items[] in manifest order, each with path, source_filename, sha256, outcome (accepted or resumed), and media_id. data.failed[] carries items that did not land.
media show#
Read one media object, including its current revision.
media show <id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The med_ object id. |
screenrig media show med_01EXAMPLEIMAGE0000000000media download#
Write a stored rendition to disk for inspection. Never print the bytes.
media download <id> [--output FILE]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The med_ object id. |
--output FILE | path | no | ./<id>.<ext> in the current directory | A file path, not a directory. An existing file is overwritten. The extension follows the content type. |
screenrig media download med_01EXAMPLEIMAGE0000000000 --output ./lobby.webpThe envelope reports media_id, path, bytes, sha256, content_type, primitive, filename, optional source_filename, and width / height. It never carries pixels. Use this to proof a generated still before it goes on glass.
media list#
List media objects, optionally filtered by tag and primitive.
media list [--tag TAG] [--primitive image|video|audio]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--tag TAG | string | no | none | Match ^[A-Za-z0-9]{1,32}$. Untagged objects are omitted whenever this filter is present. |
--primitive | string | no | all | image, video, or audio. Restrict the listing to stills, clips, or soundtrack tracks. |
screenrig media list --tag LobbyLoop --primitive videomedia update#
Change or clear the tag on one object. Nothing else about a media object is patchable.
media update <id> (--tag TAG | --tags TAG1,TAG2 | --clear-tag) [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The med_ object id. |
--tag TAG | string | one of the two | none | The replacement tag, one to 32 letters or digits. |
--clear-tag | boolean | one of the two | off | Send null and leave the object untagged. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig media update med_01EXAMPLEIMAGE0000000000 --tag LobbyLoopmedia delete#
Delete one media object.
media delete <id> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The med_ object id. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig media delete med_01EXAMPLEIMAGE0000000000Applications
An application is one already-built static directory with a root index.html. The packer injects the browser SDK at _screenrig/runtime.js and adds its script tag, so the app reaches window.screenrig with no build step and nothing to add to its dependencies. Nothing in the directory is ever built or executed during packing.
app pack#
Pack a static directory into a deterministic archive without uploading it.
app pack <directory> [--output FILE]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<directory> | path | yes | none | The built static directory. It needs a root index.html. |
--output FILE | path | no | nothing is written | Also write the archive to this path. Without it the command reports the archive and keeps it in memory. |
screenrig app pack ./lobby-board --output ./lobby-board.tgzThe envelope reports sha256, compressed_bytes, expanded_bytes, file_count, the entry list, and whether the SDK runtime was injected. app upload packs the directory itself, so reach for this command only when you want to inspect the archive first.
Applications run under a content security policy that blocks inline code. Packing moves inline style and script blocks into files under _screenrig/inline/ and links them in. Inline event handlers such as onclick= and javascript: URLs cannot be moved; packing refuses them and names the file and line.
app upload#
Pack a directory, publish it as a new application release, and wait for the publication to succeed.
app upload <directory> [--name NAME] [--no-wait] [--poll-ms MS] [--timeout MS]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<directory> | path | yes | none | The built static directory. |
--name NAME | string | no | none | Application name, at most 120 characters. |
--no-wait | boolean | no | off | Return before the publication operation finishes. Run operations wait before pinning the release into a playlist. |
--poll-ms MS | integer | no | 1000 | Interval between operation polls while waiting. |
--timeout MS | integer | no | 120000 | Deadline for the wait. It also bounds each underlying request. |
screenrig app upload ./lobby-board --name "Lobby board"Read three fields: data.operation.state is succeeded, data.application.release_id is the rel_ id an application primitive pins, and data.application.id is the app_ id the kv commands take. A succeeded operation, not a release id on its own, is what means the release is usable.
Every app upload creates a new application and a first immutable release. To publish a later release of the same app, use app update. A playlist keeps serving the release it already pins until you edit that primitive.
app update#
Publish a new release of an existing application, preserving identity, name, and application K/V.
app update <id> <directory> [--expect-rev REVISION] [--no-wait] [--poll-ms MS]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The app_ application id. |
<directory> | path | yes | none | The built static directory. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
--no-wait | boolean | no | off | Return before the publication operation finishes. Run operations wait before pinning the release into a playlist. |
--poll-ms MS | integer | no | 1000 | Interval between operation polls while waiting. |
screenrig app update app_01EXAMPLEAPP00000000000 ./lobby-boardWait for data.operation.state: "succeeded", then use playlist replace-release to review the exact page, primitive, new release, and affected screens. Apply with the returned impact token and an optional revision guard as described in the playlist workflow. Existing playlist pins remain unchanged until apply. On a revision conflict, read the app again before deciding whether to retry; do not create a replacement application just to bypass the conflict.
app list#
List the project's applications.
app listNo parameters.
screenrig app listapp show#
Read one application.
app show <id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The app_ application id. |
screenrig app show app_01EXAMPLEAPP00000000000latest_ready_release is absent until a first publish reaches ready. Publish state lives on the operation and the release, not on the application.
Compose
Local unbilled stills for slide-deck-like experiences. The compositing language stays on Compose. Presentable posters use media generate, not these commands.
compose catalog#
Print the fail-closed catalog of page keys, named regions, region fields, card plates, enter and motion enums, viewing distances, installed fonts, and examples.
compose catalogNo parameters.
screenrig compose catalogcompose render#
Render a spec to a directory of region PNGs and manifest.json.
compose render <file> [--output DIRECTORY] [--combined] [--target-width PX --target-height PX] [--safe-area] [--open] [--lint-only]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<file> | path | yes | none | The JSON spec. Page width and height define the canvas. |
--output DIRECTORY | path | no | the spec path with .json removed | Where to write region PNGs and manifest.json. A directory, not a .png. |
--combined | boolean | no | off | Also write a flattened PNG for inspection. Default agent output stays layered. |
--target-width PX / --target-height PX | number | no | unset | Physical content viewport for quality checks. Both or neither. These flags do not resize the PNGs. |
--safe-area | boolean | no | off | Warn when measured text ink crosses a 5% TV-safe margin. Nonblocking. |
--open | boolean | no | off | Open the combined PNG with the platform opener. Implies --combined. Agent vision reads file paths instead. |
--lint-only | boolean | no | off | Compose in memory and report lint without writing files. |
screenrig compose render ./lobby-welcome.json --output ./lobby-welcomeNever print image bytes. Read manifest.json and the region PNGs before any upload.
compose batch#
Render a deck and write a contact-sheet preview.
compose batch <file> --output DIRECTORY [--only ID] [--target-width PX --target-height PX] [--safe-area] [--lint-only]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<file> | path | yes | none | The same JSON as compose render, including { "pages": [ { "id": "intro", ... } ] }. |
--output DIRECTORY | path | yes | none | Destination for layered PNGs, manifest.json, a contact sheet, and a batch manifest. |
--only ID | string | no | unset | Render one page and mark the others not_selected. |
--target-width PX / --target-height PX | number | no | unset | Same quality checks as compose render. |
--safe-area | boolean | no | off | Same TV-safe warning as compose render. |
--lint-only | boolean | no | off | Report lint without writing files. |
screenrig compose batch ./deck.json --output ./rendered --target-width 3840 --target-height 2160 --safe-areaOne command accepts 1 to 2000 pages. compose render of a deck file is enough when you do not need the contact sheet.
Application key/value
Key/value storage belongs to one application and is binary-safe. Every command takes --app-id with the app_ id from app upload, never a release id. Use exactly one value mode per write.
kv list#
List the keys stored for one application.
kv list --app-id ID| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--app-id ID | string | yes | none | The app_ application id. |
screenrig kv list --app-id app_01EXAMPLEAPP00000000000kv get#
Read one key.
kv get --app-id ID <key>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--app-id ID | string | yes | none | The app_ application id. |
<key> | string | yes | none | The key to read. |
screenrig kv get --app-id app_01EXAMPLEAPP00000000000 menukv set#
Write one key from JSON, a file, or base64 bytes.
kv set --app-id ID <key> --json-value JSON [--expect-rev REVISION]
kv set --app-id ID <key> --file FILE [--content-type TYPE] [--expect-rev REVISION]
kv set --app-id ID <key> --value-base64 BASE64 [--content-type TYPE] [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--app-id ID | string | yes | none | The app_ application id. |
<key> | string | yes | none | The key to write. |
--json-value JSON | string | one mode | none | A JSON value, canonicalized before it is sent, always stored as application/json. An explicit application/json content type is also accepted. |
--file FILE | path | one mode | none | Read the value bytes from a file, or stdin with -. Defaults to application/octet-stream. |
--value-base64 BASE64 | string | one mode | none | Canonical padded standard base64 without whitespace, at most 1,398,104 characters. Defaults to application/octet-stream. |
--content-type TYPE | string | no | application/octet-stream for bytes | One to 127 characters. |
--expect-rev REVISION | string | no | none | Write only if the stored entry is still at this revision. |
screenrig kv set --app-id app_01EXAMPLEAPP00000000000 menu --json-value '{"soup":"lentil"}'kv delete#
Delete one key.
kv delete --app-id ID <key> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--app-id ID | string | yes | none | The app_ application id. |
<key> | string | yes | none | The key to delete. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig kv delete --app-id app_01EXAMPLEAPP00000000000 menuPlaylists
A playlist is a JSON document of pages. playlist init prepares a full-screen document from ordered inputs. playlist create and playlist update read that document from a file; the shape is summarised under the commands.
playlist init#
Prepare an editable full-screen playlist from local media files, ready media IDs, application release IDs, or HTTPS URLs.
playlist init <inputs...> --name NAME --output FILE [--overwrite] [--screen-id ID]
[--target-width PX --target-height PX] [--duration-ms MS] [--fit contain|cover|fill]
[--no-transcode] [--no-progress] [--poll-ms MS]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<inputs...> | files, med_ IDs, rel_ IDs, or HTTPS URLs | yes | none | Playback order. Local image/video files upload and wait for readiness unless --no-transcode. Existing media must already be ready. HTTPS URLs must contain no username or password and are not fetched. |
--name NAME | string | yes | none | Playlist display name. |
--output FILE | path | yes | none | Write the editable {name, pages} document. Parent directories are created. Exclusive by default. |
--overwrite | boolean | no | off | Replace an existing output file atomically. |
--screen-id ID | string | no | none | Read the target screen's dimensions and revision. --screen remains an alias. |
--target-width PX / --target-height PX | integer | together | unset | Override canvas size. Both or neither. Required when no screen is supplied or the screen reports unknown or multiple surfaces. |
--duration-ms MS | integer | no | 8000 | Duration for non-video pages. |
--fit | string | no | contain | contain, cover, or fill for image and video pages. Applications and iframes use fill. |
--no-transcode | boolean | no | off | Upload accepted file bytes unchanged. |
--no-progress | boolean | no | off | Suppress upload progress on stderr. |
--poll-ms MS | integer | no | 1000 | Upload polling interval. |
screenrig playlist init med_POSTER med_VIDEO --name "Lobby loop" --screen-id scr_LOBBY --output lobby.jsonPreparation writes the local document and may upload media; it neither creates a remote playlist nor assigns a screen. Inspect the document and preview, then screen publish. See Prepare, publish, and edit playlists.
playlist templates#
Print the local catalog of slide template ids.
playlist templatesNo parameters.
screenrig playlist templatesTemplates run locally and expand to wire primitives. A template that would emit vector chrome fails with usage_error pointing at the Compose reference, because copy and chrome are composed locally and placed as one image.
playlist validate#
Validate a playlist document locally before create or update. No network.
playlist validate <file> [--lint-only]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<file> | path | yes | none | The playlist document. |
--lint-only | boolean | no | off | Report visual lint without the full write-time checks that create/update also run. |
screenrig playlist validate ./lobby-loop.jsonErrors name exact JSON paths. A pass means local shape and cross-field semantics are valid; authorization, media readiness, dynamic selector counts, durations, and remote availability still require server checks. Create and update run this check before their write.
playlist preview#
Composite every page to PNG frames for inspection. Never print pixels.
playlist preview <file|id> --output DIR [--frame-ms MS] [--contact-sheet] [--lint-only]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<file|id> | path or pl_ id | yes | none | A JSON file is local. A pl_… id uses the project API. |
--output DIR | path | yes | none | Destination directory for the frame PNGs. |
--frame-ms MS | integer | no | 1000 | Poster frame from a video primitive, 0 through 60000. |
--contact-sheet | boolean | no | off | Also write one 6-column PNG of every rest frame. |
--lint-only | boolean | no | off | Report lint without writing files. |
screenrig playlist preview ./lobby-loop.json --output ./frames --contact-sheetWrites three PNGs per page: <page-id>.rest.png, <page-id>.entry.png, and <page-id>.motion-mid.png. Iframe and application regions are labelled placeholders. Look at the contact sheet before publishing.
playlist create#
Create a playlist from a JSON document.
playlist create <file>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<file> | path | yes | none | The playlist document. |
screenrig playlist create ./lobby-loop.jsonThe envelope's data.id is the pl_ id that screen assign takes.
playlist update#
Replace a playlist document.
playlist update <id> <file> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The pl_ playlist id. |
<file> | path | yes | none | The replacement document. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig playlist update pl_01EXAMPLEPLAYLIST000000 ./lobby-loop.jsonplaylist show#
Read one playlist, including its current revision.
playlist show <id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The pl_ playlist id. |
screenrig playlist show pl_01EXAMPLEPLAYLIST000000playlist list#
List the project's playlists.
playlist listNo parameters.
screenrig playlist listplaylist delete#
Delete one playlist.
playlist delete <id> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The pl_ playlist id. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig playlist delete pl_01EXAMPLEPLAYLIST000000playlist export#
Write one playlist and every rendition it resolves into a portable bundle directory.
playlist export <id> --output DIRECTORY [--skip-applications]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The pl_ playlist id. |
--output DIRECTORY | path | yes | none | Destination directory. It must not already exist; the bundle is written through a temporary sibling and renamed into place. |
--skip-applications | flag | no | off | Drop application primitives and any page left with no primitives. The result lists the skipped primitive and page ids. |
screenrig playlist export pl_01EXAMPLEPLAYLIST000000 --output ./lobby-bundleThe bundle holds a manifest, the playlist document, and content-addressed media files. Dynamic all and tag selectors are snapshotted to exact ids, iframe URLs are preserved, and comments are excluded. Application primitives cannot be exported: by default they stop the export before anything is downloaded, and --skip-applications exports the rest.
playlist import#
Create a playlist from a bundle directory, or update an existing one from it.
playlist import <directory> [--name NAME] [--update ID [--expect-rev REVISION]]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<directory> | path | yes | none | A bundle directory written by playlist export. |
--name NAME | string | no | the bundle's playlist name | 1 to 120 characters. Import the bundle as a new playlist under this name. |
--update ID | string | no | a new playlist is created | Update this pl_ playlist instead of creating one. Accepts optional --expect-rev. |
--expect-rev REVISION | string | no | none | The destination playlist's current revision. |
screenrig playlist import ./lobby-bundle
screenrig playlist import ./lobby-bundle --name "Lobby loop (copy)"Playlist display names may repeat. Import creates a separate playlist even when its name matches; use --update with an ID to replace an existing playlist.
Import validates the whole bundle locally before it touches the project: manifest, document, paths, media kinds, lengths, and hashes. Existing media are reused on an exact filename, media type, byte length, hash, and tag match; the rest are uploaded without conversion, and the playlist is written only after every object is ready.
Import never deletes or prunes remote media. If a later step fails after an upload has started, the error lists the objects confirmed ready and says whether the playlist write began. Retrying the same bundle reuses those objects.
The playlist document
A playlist is name plus pages, with an optional audio soundtrack. Each page is either a template page or a primitives page; do not mix the two on one page. A full page carries id, canvas, transition, advance, optional visibility, optional audio_cue, and primitives.
The five primitives are image, uploaded video, live stream, and web iframe and application. Copy and chrome are not wire types: compose them locally, upload the still, and place it as an image.
A stream takes one or two ordered sources with unique protocols and a ready image from the same project as fallback_media_id. Use at most one stream per page and duration-based advance. HLS uses a public HTTPS URL; optional direct UDP/MPEG-TS multicast on Android and Qt uses an IPv4 group in 239/8 and a port. Other Players select the HLS alternative or display the fallback. Stream bytes are not cached for replay. Audio defaults to muted. Preview displays the fallback; playlist bundle import/export does not support streams in this version.
{
"id": "live",
"primitive": "stream",
"sources": [
{
"protocol": "udp-mpegts",
"group": "239.10.0.1",
"port": 5000
},
{
"protocol": "hls",
"url": "https://example.com/live.m3u8"
}
],
"fallback_media_id": "med_fallback",
"muted": true,
"rect": {
"x": 0,
"y": 0,
"width": 1920,
"height": 1080
},
"layer": 0,
"content_fit": "contain"
}Use this primitive on a page with advance: {"mode":"duration","after_ms":30000}. Replace the example feed and fallback ID. Player requirements and transport setup.
Every primitive is flat: id, primitive, that primitive's own fields, then rect, layer, content_fit, and optional enter. There is no nested content object.
Image and video primitives require a selector. selector.by is id with one ready media_id and one_at_a_time false, ids with 1 to 1,024 unique ready media_ids, all for every ready object of that primitive, or tag matching ^[A-Za-z0-9]{1,32}$. Do not put media_id on the primitive itself or send server-resolved items. An application primitive pins one release_id and an iframe primitive takes a public HTTPS src and a title, so neither of those takes a selector. Publish accepts that HTTPS URL after rejecting private addresses; it does not check whether the target allows framing. A blocked page fails at runtime and is recorded in the event log. Both web primitives require content_fit of fill, because neither has an intrinsic size.
advance.mode is duration with after_ms, media_end on a page with exactly one image or video primitive, or application with max_ms when the app ends its own turn by calling window.screenrig.nextPage(). An application page needs exactly one primitive marked controller; every other mode forbids it.
transition.type is crossfade, swipe-left, swipe-right, swipe-up, or swipe-down, with a required duration_ms from 0 through 60000. The default page is a 200 ms crossfade. An optional enter object on a primitive adds one object animation: fade-up, fade-down, fade-left, fade-right, fade-in, zoom-in, or zoom-out.
An optional page visibility object limits when the page plays: a required enabled boolean, optional civil from and until bounds, and up to 16 recurring windows of days and civil times. Every playlist keeps at least one page with no visibility field at all, and a screen running a scheduled playlist needs a timezone. Set that timezone first; screen assign and playlist update both refuse locally without one and name the command that fixes it.
An optional top-level audio object adds a soundtrack that plays continuously while pages change. tracks lists 1 to 32 entries in playback order, each with a unique id (the page id pattern) and the media_id of a ready audio media object. loop defaults to true and restarts at the first track after the last; volume defaults to 1 and ranges from 0 to 1, multiplied by the Player volume. An ordinary page may carry audio_cue with a track id from that list and an optional restart boolean (default false): when the page appears, the Player switches to that track from its start, restarting it only when it is already playing and restart is true. Cues are hints, adslot pages cannot carry them, and a cue without a soundtrack is rejected. playlist update replaces the whole document, so omitting audio removes the soundtrack. Soundtrack authoring.
Screens
A screen is one Player. These commands read it, point it at a playlist, and manage its life. Revision preconditions are optional on writes.
Tags group screens into fleets. Each screen carries 0 to 16 unique tags of 1 to 32 letters or digits, set with screen tag. screen assign, screen reload, screen toast, screen tag, and screen screenshot take one screen id, several ids, or --tag TAG, and a fleet answer reports every screen: see Fleet results.
screen list#
List screens. Archived screens are omitted unless you ask for them.
screen list [--state archived] [--tag TAG]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--state | string | no | active screens | The only accepted value is archived, which lists archived screens only. |
--tag TAG | string | no | every screen | Lists only screens carrying this exact tag. |
screenrig screen list --tag LobbyEach row carries the screen's tags. With --human, the table adds a TAGS column when any listed screen has tags.
screen show#
Read one screen, including revision, assignment, and liveness.
screen show <id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The scr_ screen id. |
screenrig screen show scr_01EXAMPLESCREEN00000000tags lists the screen's fleet tags. effective_playlist, takeover, and playlist_schedule report what the screen plays and why; see Schedules and takeover. health and the Player's host capabilities are in Device health and power. online is true while a Player is connected, including a short reconnect window. Optional last_online_at and last_ip appear after the first connect. Optional observation reports the surfaces a Player has painted: id, width, height, pixel_ratio, and presentation. All of these are read-only; screen update cannot send them, and an archived screen still returns its row.
screen update#
Patch a screen's name, playlist assignment, or timezone in one call.
screen update <id> [--name NAME] [--playlist-id ID] [--timezone ZONE] [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The scr_ screen id. |
--name NAME | string | no | unchanged | Display name for the screen. |
--playlist-id ID | string | no | unchanged | The pl_ playlist this screen should run. |
--timezone ZONE | string | no | unchanged | An IANA zone identifier such as America/Los_Angeles. Validated against the server's zone database. A patch never clears a zone. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig screen update scr_01EXAMPLESCREEN00000000 --name "Lobby"screen publish#
Create a new playlist from a prepared file and assign it to a screen.
screen publish <id> <file> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The scr_ screen id. |
<file> | path | yes | none | The prepared playlist document, or - for stdin. |
--expect-rev REVISION | string | no | none | Optional screen revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig screen publish scr_LOBBY lobby.jsonThis creates a new playlist and assigns it. It does not update an existing playlist by name. Inspect the prepared document and preview first. Repeat identical input to resume an ambiguous failure. Assignment readback does not prove playback; request a screenshot separately. See Prepare, publish, and edit playlists.
screen assign#
Point a screen, or a fleet of screens, at a playlist.
screen assign (<id>... | --tag TAG) --playlist-id ID [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id>... | string | one of <id> or --tag | none | One or more scr_ screen ids. Several ids run as one fleet request. |
--tag TAG | string | one of <id> or --tag | none | Every active screen carrying this tag. |
--playlist-id ID | string | yes | none | The pl_ playlist to run. |
--expect-rev REVISION | string | no | none | One screen id only. Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig screen assign scr_01EXAMPLESCREEN00000000 --playlist-id pl_01EXAMPLEPLAYLIST000000
screenrig screen assign --tag Lobby --playlist-id pl_01EXAMPLEPLAYLIST000000If the playlist schedules any page and the screen has no timezone, the CLI refuses before sending anything and names the screen set-timezone command to run. No revision is consumed by that refusal. In a fleet, each screen applies that rule on its own: a screen with no timezone fails in its result and the others are assigned.
To publish to a fleet, create the playlist once with playlist create, then run screen assign --tag TAG --playlist-id ID. screen publish targets one screen.
screen set-timezone#
Set the civil timezone a scheduled playlist is read in.
screen set-timezone <id> --timezone ZONE [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The scr_ screen id. |
--timezone ZONE | string | yes | none | An IANA zone identifier such as Europe/Berlin. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig screen set-timezone scr_01EXAMPLESCREEN00000000 --timezone America/Los_AngelesPage schedules are civil times, so they are meaningless without a zone. Changing the zone remints the manifest revision.
screen archive#
Hide a screen and darken its glass.
screen archive <id> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The scr_ screen id. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig screen archive scr_01EXAMPLESCREEN00000000Archiving hides the screen from the default listing and darkens the glass. It does not unbind the Player.
screen unarchive#
Restore an archived screen to the default listing.
screen unarchive <id> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The scr_ screen id. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig screen unarchive scr_01EXAMPLESCREEN00000000A Player can also archive its own screen: a confirmed reset on a native Player, or unpairing a browser Player. screen show then reports archive_reason as device_reset or device_unpair (project for an archive you request), with archived_at. The screen keeps its device binding for every reason, so a Player that still holds that key or device cookie stays dark and resumes on unarchive with no re-pairing. The Player that was reset or unpaired drops its key or cookie and pairs again as a new screen. A key replaced by a confirmed screen recovery stays retired.
screen delete#
Delete a screen.
screen delete <id> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The scr_ screen id. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig screen delete scr_01EXAMPLESCREEN00000000Delete is not a way to detach a Player: a live screen returns screen_archive_required. Archive it instead.
screen rotate-public-id#
Issue a new public id for a screen.
screen rotate-public-id <id> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The scr_ screen id. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig screen rotate-public-id scr_01EXAMPLESCREEN00000000screen reload#
Ask a screen's Player, or every Player in a fleet, to reload once.
screen reload (<id>... | --tag TAG) [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id>... | string | one of <id> or --tag | none | One or more scr_ screen ids. Several ids run as one fleet request. |
--tag TAG | string | one of <id> or --tag | none | Every active screen carrying this tag. |
--expect-rev REVISION | string | no | none | One screen id only. Optional revision precondition. |
screenrig screen reload scr_01EXAMPLESCREEN00000000
screenrig screen reload --tag LobbyThe answer carries a reload_id and an expires_at ten minutes later. A web Player reloads at its next page boundary; a native Player reconnects, refetches its manifest, and checks for an update. A Player ignores a reload within ten minutes of the last one it acted on. Reload works on active and archived screens and does not change the screen revision.
screen toast#
Post one transient message as stage chrome on a live screen or a fleet.
screen toast (<id>... | --tag TAG) --text TEXT [--level info] [--duration-ms MS]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id>... | string | one of <id> or --tag | none | One or more scr_ screen ids. Several ids run as one fleet request. |
--tag TAG | string | one of <id> or --tag | none | Every active screen carrying this tag. |
--text TEXT | string | yes | none | One to 120 characters, line feed as the only break, at most three lines. |
--level LEVEL | string | no | info | info, alert, or error. The hosted Player shows error toasts. Info and alert are diagnostic levels that may be suppressed; command success does not establish visibility. |
--duration-ms MS | integer | no | 10000, applied by the server | Between 2000 and 60000 when supplied. |
screenrig screen toast scr_01EXAMPLESCREEN00000000 --text "Lobby closed"
screenrig screen toast --tag Lobby --text "Closing in ten minutes"A toast is not a primitive: it occupies no canvas slot, has no layer, and takes no part in readiness or crossfade. Latest wins, there is no queue and no cancel, and the accepted envelope is { expires_at } only. Never put credentials or other secret material in the text.
screen screenshot#
Capture one still of an active screen, or of every screen in a fleet, and write it to disk.
screen screenshot <id> [--output FILE] [--timeout MS] [--poll-ms MS]
screen screenshot (<id> <id>... | --tag TAG) [--output DIRECTORY] [--concurrency N] [--timeout MS] [--poll-ms MS]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id>... | string | one of <id> or --tag | none | One or more scr_ screen ids. |
--tag TAG | string | one of <id> or --tag | none | Every active screen carrying this tag, with no screen-count cap. |
--output FILE | path | no | ./<id>.webp | For one screen, a file path; an existing file is overwritten without a prompt. For several ids or --tag, a directory, created if missing, that receives one <screen_id>.webp per screen. |
--concurrency N | integer | no | 4 | Captures in flight at once for several ids or --tag, from 1 to 8. |
--timeout MS | integer | no | 35000 | How long to wait for the capture. |
--poll-ms MS | integer | no | 500 | Interval between capture-status polls. |
screenrig screen screenshot scr_01EXAMPLESCREEN00000000 --output ./lobby.webpscreenrig screen screenshot --tag Lobby --output ./lobby-shotsThe command blocks until the WebP is on disk; there is no --no-wait. The envelope reports screen_id, capture_id, path, bytes, sha256, width, and height, and never image bytes. Latest wins: a later request replaces an in-flight capture.
For several ids or --tag, the answer is a fleet result with action: "screenshot", the selector, and the output directory; each ok result carries path, bytes, sha256, width, and height. With --tag, the CLI first lists the tagged screens, following every list page; each list page is billed and the captures themselves are free, and several ids need no list. --idempotency-key is refused in this form, because each capture uses its own key. If a capture fails unexpectedly, that screen's result is unexpected_error, no new captures start, the screens still waiting report not_attempted, and the exit code is 1.
screen tag#
Set, add, remove, or clear the tags on a screen or a fleet.
screen tag (<id>... | --tag TAG) (--set TAGS | --add TAGS | --remove TAGS | --clear) [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id>... | string | one of <id> or --tag | none | One or more scr_ screen ids. Several ids run as one fleet request. |
--tag TAG | string | one of <id> or --tag | none | Every active screen carrying this tag. |
--set TAGS | string | exactly one action | none | Replace the whole set with these comma-separated tags, such as Lobby,Floor2. |
--add TAGS | string | exactly one action | none | Add these comma-separated tags. |
--remove TAGS | string | exactly one action | none | Remove these comma-separated tags. |
--clear | boolean | exactly one action | off | Remove every tag. |
--expect-rev REVISION | string | no | none | One screen id only. Optional revision precondition. |
screenrig screen tag scr_01EXAMPLESCREEN00000000 --set Lobby,Floor2
screenrig screen tag scr_01EXAMPLESCREEN00000000 --add Spring
screenrig screen tag --tag Lobby --add SpringA tag is 1 to 32 letters or digits, and a screen carries at most 16 unique tags. Tags select fleets; they are never authorization and never reach the Player. Changing tags bumps the screen revision, not the manifest revision. For one screen the envelope is the updated screen. --add and --remove read the screen first and write the new set guarded by --expect-rev or the revision just read, so a concurrent change returns revision_conflict (exit 6) instead of being overwritten; rerun the command. In a fleet, --add and --remove apply against each screen's stored set atomically, and a screen that would pass 16 tags fails in its own result with invalid_request.
Fleet results
With several screen ids or --tag, screen assign, screen reload, screen toast, and screen tag run one metered action with no screen-count cap. The CLI follows bounded result pages automatically, waits when control budgets are exhausted, and retains the same key and selected fleet throughout. --tag selects active screens only; ids may name any screen. One screen id keeps the single-screen answer. Fleet commands take no --expect-rev, because revision guards are per screen. Each fleet command carries an idempotency key. After an interrupted request, such as a lost connection, rerun the identical command to replay finished screens without repeating their side effects. After a definite answer with failures, retry only the failed ids: rerunning the whole command acts on every screen again. Reusing the same explicit --idempotency-key within 24 hours returns the recorded answer instead.
The envelope keeps ok: true. Partial success is a normal answer, not a transport error:
{
"ok": true,
"data": {
"action": "reload",
"matched": 3, "succeeded": 2, "failed": 1,
"results": [
{ "screen_id": "scr_LOBBY", "status": "ok", "reload": { "reload_id": "…", "expires_at": "…" } },
{ "screen_id": "scr_GONE", "status": "failed", "problem": { "code": "not_found", "status": 404 } },
{ "screen_id": "scr_ENTRANCE", "status": "ok", "reload": { "reload_id": "…", "expires_at": "…" } }
]
},
"warnings": [{ "code": "fleet_partial_failure", "message": "1 of 3 screens failed; 2 succeeded." }]
}An ok result carries the single-screen result: revision for assign, revision and tags for tag changes, reload for reload, and toast for toast. A failed result carries the problem that screen's own request would have returned. Per-screen limits still apply inside a fleet action, so a fleet reload or toast can report rate_limited for a screen that was just reloaded or toasted; retry that screen id later. Per-screen limits are 6 reloads and 20 toasts a minute. Fleet reloads and toasts also draw on a project budget of 600 screens a minute: the server pauses when the remaining budget is exhausted, and the CLI honors Retry-After before continuing automatically.
| Outcome | Exit code | Warning |
|---|---|---|
| Every matched screen succeeded | 0 | none |
| Some or all screens failed | The first failed screen's code, such as 4 for not_found or 6 for revision_conflict | fleet_partial_failure |
| No screen matched | 0 | fleet_no_match |
| Malformed selector or action | 2 or the request's own code; no screen changes | none |
Schedules and takeover
The server decides what each screen plays, in this order: its takeover, else the first playlist schedule entry whose windows match the screen's civil time, else the playlist assigned with screen assign. Switches land within about a minute of a boundary, and Players need no update.
screen show, screen list, and every command below return effective_playlist ({id, source, entry_id?, until?}, where source is takeover, schedule, or default), takeover, and playlist_schedule. until is the next moment the choice is known to change. screen show --human prints it in the screen's timezone, as in Playing: pl_LUNCH (schedule entry lunch until 2026-08-14 15:00 America/Los_Angeles) (UTC, labelled, when the screen has no zone); JSON output keeps RFC 3339 instants. Human schedule tables are labelled with the screen timezone, and screen list --human adds a PLAYING column when any listed screen is on a takeover or schedule entry.
| Problem code | Exit | Meaning |
|---|---|---|
invalid_request | 8 | The screen has no assigned default playlist (next is screen assign), a schedule targets a screen with no timezone (next is screen set-timezone), or a named playlist cannot be assigned. |
screen_archived | 5 | An archived screen refuses schedules and takeovers. next is screen unarchive. |
revision_conflict | 6 | The --expect-rev revision was stale. |
rate_limited | 7 | Schedule and takeover writes share per-minute limits: 600 per project, 20 per screen, and 60 per IP. Wait for retry_after_seconds. |
With one screen id, these commands return the updated screen and --expect-rev guards its revision. Several ids or --tag run as one fleet request with the fleet envelope and exit rules; each screen fails alone, and the CLI pauses and resumes automatically when the project budget is exhausted.
screen schedule show#
Show a screen's playlist schedule and the playlist it currently selects.
screen schedule show <id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The scr_ screen id. |
screenrig screen schedule show scr_CAFEThe JSON output can go straight back to screen schedule set --file.
screen schedule set#
Replace the playlist schedule on a screen or a fleet.
screen schedule set (<id>... | --tag TAG) --file FILE [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id>... | string | one of <id> or --tag | none | One or more scr_ screen ids. |
--tag TAG | string | one of <id> or --tag | none | Every active screen carrying this tag. |
--file FILE | path | yes | none | The schedule document, or - for stdin. |
--expect-rev REVISION | string | no | none | One screen id only. Optional revision precondition. |
screenrig screen schedule set scr_CAFE --file dayparts.json
screenrig screen schedule set --tag Cafe --file dayparts.jsonEvery playlist a schedule names is validated as an assignment, and the screen needs a timezone and an assigned default playlist. Outside every window the assigned playlist plays. If a scheduled playlist later cannot be shown, its entries are skipped and screen.playlist_unavailable is recorded.
screen schedule clear#
Remove the playlist schedule from a screen or a fleet.
screen schedule clear (<id>... | --tag TAG) [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id>... | string | one of <id> or --tag | none | One or more scr_ screen ids. |
--tag TAG | string | one of <id> or --tag | none | Every active screen carrying this tag. |
--expect-rev REVISION | string | no | none | One screen id only. Optional revision precondition. |
screenrig screen schedule clear --tag Cafescreen takeover#
Show one playlist on a screen or a fleet ahead of the schedule and the assignment.
screen takeover [set] (<id>... | --tag TAG) --playlist-id ID [--until TIME | --for DURATION] [--reason TEXT] [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id>... | string | one of <id> or --tag | none | One or more scr_ screen ids. |
--tag TAG | string | one of <id> or --tag | none | Every active screen carrying this tag. |
--playlist-id ID | string | yes | none | The pl_ playlist to show. |
--until TIME | string | no | held until cleared | A strict RFC 3339 instant with seconds and an offset, in the form YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DDTHH:MM:SS-07:00, sent normalized to UTC; strictly in the future and at most 7 days ahead. Or none to hold until screen takeover clear. |
--for DURATION | string | no | held until cleared | 30m, 2h, or 3d, up to 6d23h59m, converted to an until from this computer's clock. 7d is refused locally; use --until for a full 7 days. Not with --until. |
--reason TEXT | string | no | none | An operator note on the takeover and its events. Trimmed, at most 120 characters, and no control characters. |
--expect-rev REVISION | string | no | none | One screen id only. Optional revision precondition. |
screenrig screen takeover scr_LOBBY --playlist-id pl_DRILL --for 30m --reason "Fire drill"
screenrig screen takeover --tag Lobby --playlist-id pl_LAUNCH --for 2h
screenrig screen takeover scr_A scr_B --playlist-id pl_NOTICE --until nonescreen takeover ID is short for screen takeover set ID. A new takeover replaces the previous one. When it ends, the screen returns to its schedule or assigned playlist on its own. A takeover whose playlist cannot be shown ends. A --for rerun after an ambiguous failure computes a new end and is sent as a new request.
screen takeover clear#
End the takeover on a screen or a fleet.
screen takeover clear (<id>... | --tag TAG) [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id>... | string | one of <id> or --tag | none | One or more scr_ screen ids. |
--tag TAG | string | one of <id> or --tag | none | Every active screen carrying this tag. |
--expect-rev REVISION | string | no | none | One screen id only. Optional revision precondition. |
screenrig screen takeover clear --tag LobbyThe schedule file
A schedule lists 1 to 32 entries in priority order; the first entry that matches wins. Each entry has 1 to 16 windows in the screen timezone. days uses mon to sun, and start and end are HH:MM; omit both for the whole day. An end at or before start crosses midnight and belongs to the start day. Optional from (inclusive) and until (exclusive) are civil minutes such as 2026-12-24T18:00, with no offset. An omitted id becomes entry_N. The CLI checks this shape before sending anything. Breakfast, lunch, and dinner dayparts:
{
"entries": [
{ "id": "breakfast", "playlist_id": "pl_BREAKFAST",
"windows": [{ "days": ["mon", "tue", "wed", "thu", "fri"], "start": "06:00", "end": "11:00" },
{ "days": ["sat", "sun"], "start": "08:00", "end": "12:00" }] },
{ "id": "lunch", "playlist_id": "pl_LUNCH",
"windows": [{ "days": ["mon", "tue", "wed", "thu", "fri"], "start": "11:00", "end": "15:00" }] },
{ "id": "dinner", "playlist_id": "pl_DINNER",
"windows": [{ "days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"], "start": "17:00", "end": "22:00" }] }
]
}A playlist schedule chooses between playlists; page visibility inside one playlist still chooses between its pages.
Schedule and takeover events
events list and events follow print screen.playlist_switched (playlist_id, previous_playlist_id, source, entry_id, until), screen.takeover_started (playlist_id, until, where none means held until cleared, and reason), screen.takeover_ended (reason expired, cleared, replaced, playlist_deleted, or playlist_unavailable), and the warning screen.playlist_unavailable (playlist_id, entry_ids, and code playlist_deleted or playlist_unavailable).
Deleting a playlist that a live screen can still show fails with resource_conflict (exit 5): assigned, named by a schedule entry, held by a takeover, or effective. next points at screen list.
Device health and power
Every paired Player reports device health on session start, every fifteen minutes, and at once when its display connection or power changes. It also declares the host capabilities it supports: reboot, and the display power methods it can use (display_power.cec, display_power.ddc, display_power.backlight, or display_power.scrim, an on-screen blackout). screen show returns both.
screen show returns health as data.health with the server's reported_at; stale turns true after 35 minutes without a report, and the last report stays visible. With --human it prints a Health block: report time, device and Player uptime, memory in use, CPU load and cores, temperature, display connection and power, network kind and Wi-Fi signal, and crashes and renderer restarts in the last 24 hours. screen list --human adds a HEALTH column when any listed screen needs attention: display disconnected, hot (80 °C or more), crashing (3 or more crashes in 24 hours), or stale.
Transitions arrive as screen.health_changed events, printed by events list and events follow with a compact changes= value such as display_disconnected,temperature_high temperature_c=82. Power commands append screen.reboot_requested and screen.display_changed. Like every screen.* event, they are free on the listen stream and as webhook deliveries.
| Problem code | Exit | Meaning |
|---|---|---|
reboot_unsupported | 5 | The screen's Player does not declare reboot. Nothing was sent; screen reload refreshes content without a device reboot. |
resource_conflict | 5 | The screen is still waiting to pair and has no Player to switch yet. A display schedule can still be set. |
invalid_request | 8 | A display schedule targets a screen with no timezone (next is screen set-timezone), or the file is malformed. |
rate_limited | 7 | Reboots: 2 per screen per 10 minutes and 600 per project a minute. Display writes: 10 per screen and 600 per project a minute. Wait for retry_after_seconds. |
Several ids or --tag run as one fleet request with the fleet envelope and exit rules.
screen reboot#
Reboot a screen's device, or a fleet's devices.
screen reboot (<id>... | --tag TAG) [--yes] [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id>... | string | one of <id> or --tag | none | One or more scr_ screen ids. |
--tag TAG | string | one of <id> or --tag | none | Every active screen carrying this tag. |
--yes | boolean | for several ids or --tag | off | Confirms a fleet reboot. The CLI never prompts. |
--expect-rev REVISION | string | no | none | One screen id only. Optional revision precondition. |
screenrig screen reboot scr_LOBBY
screenrig screen reboot --tag Lobby --yesThe Player reboots the device, not just itself. A reboot request expires after ten minutes. A refused or failed request, and an exact replay of the same request, do not count toward the per-screen limit.
screen display#
Turn a screen's display, or a fleet's displays, on or off now, or clear that override.
screen display [set] (<id>... | --tag TAG) --power on|off [--until TIME | --for DURATION] [--expect-rev REVISION]
screen display clear (<id>... | --tag TAG) [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id>... | string | one of <id> or --tag | none | One or more scr_ screen ids. A trailing on or off after the ids is the same as --power. |
--tag TAG | string | one of <id> or --tag | none | Every active screen carrying this tag. |
--power on|off | string | yes, for set | none | The power to apply now. |
--until TIME | string | no | the display schedule's next boundary | A strict RFC 3339 instant, strictly in the future and at most 7 days ahead. |
--for DURATION | string | no | the display schedule's next boundary | 30m, 2h, or 3d, up to 6d23h59m. Not with --until. |
--expect-rev REVISION | string | no | none | One screen id only. Optional revision precondition. |
screenrig screen display scr_LOBBY --power off --for 2h
screenrig screen display --tag Lobby --power on
screenrig screen display clear --tag Lobbyscreen display is a manual override of the display schedule. Without --until or --for it ends at the schedule's next boundary; a later display-schedule or timezone change moves that end to the new boundary. With no boundary in the next eight days, for example with no enabled schedule, it holds until screen display clear or a replacement. screen display clear ends the override, so the display schedule applies again; without one, the display is on. The Player applies every change at once and reports the power it achieved.
screen display-schedule#
Show, set, or clear when a screen's display is on.
screen display-schedule show <id>
screen display-schedule set (<id>... | --tag TAG) --file FILE [--expect-rev REVISION]
screen display-schedule clear (<id>... | --tag TAG) [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id>... | string | one of <id> or --tag | none | One or more scr_ screen ids; show takes one. |
--tag TAG | string | one of <id> or --tag | none | Every active screen carrying this tag. |
--file FILE | path | yes, for set | none | The display schedule, or - for stdin. |
--expect-rev REVISION | string | no | none | One screen id only. Optional revision precondition. |
screenrig screen display-schedule set scr_LOBBY --file hours.json
screenrig screen display-schedule set --tag Cafe --file hours.jsonA display schedule lists 1 to 16 windows when the display is on, in the screen timezone; outside every window the display goes to standby. The Player evaluates it on the device, so it keeps working offline. A window whose end is at or before its start crosses midnight, and a window without start and end covers the whole day. enabled: false keeps the windows and leaves the display on. The saved output of display-schedule show can be edited and sent back with set --file.
{"enabled": true, "windows": [{"days": ["mon", "tue", "wed", "thu", "fri"], "start": "07:00", "end": "19:00"}, {"days": ["sat"]}]}screen show --human prints Display: with the requested power, its source (manual override, display schedule, or default) and until when in the screen timezone, the schedule windows, and the power the Player reported, marked stale after 35 minutes without a report.
Operations
Uploads and publications run as operations. media upload 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_01EXAMPLEOPERATION00000operations 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_01EXAMPLEOPERATION00000operations 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_01EXAMPLEOPERATION00000Events
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.
events list#
Read one finite page of project events.
events list [--after CURSOR] [--limit N]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--after CURSOR | string | no | the start of the page window | Resume after this cursor. |
--limit N | integer | no | the server's page size | Requested page size. |
screenrig events list --limit 25The 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]| 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. |
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.
The default output is NDJSON: one envelope per event on its own line, rather than a single envelope. Use --human for logfmt lines.
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. A project has at most 10 webhooks. Each matching event arrives as the same JSON object events list returns.
Each delivery attempt is metered like a billed listen-stream event: 1 credit after the monthly 10,000 API and listen-stream allowance. The same events that are free on the listen stream are free here: screen.*, runtime.*, application.event, playback.page_failed, and agent.command. An attempt refused before anything is sent is not billed.
| 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. |
webhook_limit_reached | 5 | The project already has 10 webhooks. Delete one first. |
revision_conflict | 6 | The --expect-rev revision was stale. Run webhooks show, then retry. |
rate_limited | 7 | More than 20 webhooks test calls in a minute for this project. |
webhooks create#
Register an HTTPS endpoint and receive its signing secret once.
webhooks create --url URL --event-types TYPES [--description TEXT] [--disabled]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--url URL | string | yes | 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. |
--event-types TYPES | string | yes | none | 1 to 32 comma-separated exact types, such as screen.online, or prefixes ending in .*, such as screen.*. |
--description TEXT | string | no | none | At most 200 characters. |
--disabled | boolean | no | off | Create it disabled. Nothing is delivered until webhooks update --enable. |
screenrig webhooks create --url https://hooks.example.com/screenrig --event-types "screen.*,playlist.updated"The envelope is the webhook with its signing secret in data.secret (whsec_…) and the warning webhook_secret_shown_once. Store the secret in the receiver's secret store right away. No other command returns it, 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.
webhooks list#
List this project's webhooks, never their secrets.
webhooks listscreenrig webhooks listwebhooks show#
Inspect a webhook and its delivery health.
webhooks show <id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The whk_ webhook id. |
screenrig webhooks show whk_01EXAMPLEWEBHOOK00000status is active, failing while no delivery has succeeded since failing_since, or disabled. After 72 hours of continuous failure the webhook is disabled with disabled_reason: delivery_failures, and the event log records webhook.disabled. last_success_at and last_failure_at report the latest outcomes.
webhooks update#
Change a webhook's URL, event types, description, or enabled state.
webhooks update <id> [--url URL] [--event-types TYPES] [--description TEXT | --clear-description]
[--enable | --disable] [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The whk_ webhook id. |
--url URL | string | at least one change | unchanged | Checked like create. |
--event-types TYPES | string | at least one change | unchanged | Replaces the whole list. |
--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 delivery at the current event. |
--disable | boolean | at least one change | off | Stops delivery; pending deliveries fail with webhook_disabled. |
--expect-rev REVISION | string | no | none | Optional revision precondition. |
screenrig webhooks update whk_01EXAMPLEWEBHOOK00000 --enableupdate changes only the fields you pass. Events from while the webhook was disabled are not delivered after --enable.
webhooks delete#
Delete a webhook. Its pending deliveries fail with webhook_deleted.
webhooks delete <id> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The whk_ webhook id. |
--expect-rev REVISION | string | no | none | Optional revision precondition. |
screenrig webhooks delete whk_01EXAMPLEWEBHOOK00000webhooks rotate-secret#
Replace a webhook's signing secret and receive the new one once.
webhooks rotate-secret <id> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The whk_ webhook id. |
--expect-rev REVISION | string | no | none | Optional revision precondition. |
screenrig webhooks 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.
webhooks test#
Queue one webhook.test delivery to this webhook only.
webhooks test <id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The whk_ webhook id. |
screenrig webhooks test whk_01EXAMPLEWEBHOOK00000The envelope is the queued delivery. It is attempted once without retries, works on a disabled webhook, is metered like any delivery, and never changes the webhook's failure state. At most 20 test deliveries a minute per project; more is rate_limited (exit 7). Read the outcome with webhooks deliveries.
webhooks deliveries#
Read a webhook's delivery log, newest event first.
webhooks deliveries <id> [--before CURSOR] [--limit N]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The whk_ webhook id. |
--before CURSOR | string | no | the newest event | The next_cursor (whc1_…) of the previous page. |
--limit N | integer | no | 50 | Rows per page, 1 to 200. |
screenrig webhooks deliveries whk_01EXAMPLEWEBHOOK00000 --limit 20Each row is one event: event_id, event_type, state (pending, succeeded, or failed), attempts, last_status, and a fixed last_error class such as http_status, timeout, connection_failed, tls_failed, dns_failed, or url_rejected. 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
The body is the same JSON event object events list returns. The headers are ScreenRig-Webhook-Id, ScreenRig-Event-Id (stable across retries), 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);
}Playback
Playback evidence comes in two shapes. playback plays reads one row per visible start of an image or video on a screen, oldest first by received_at: proof of play for a report or an advertiser. playback list reads daily totals, one row per screen, media object, and UTC day. Both export as CSV, and the dashboard exports the same files.
Plays are kept for the plan's event retention, at least 30 days.
| Problem code | Exit | Meaning |
|---|---|---|
invalid_request | 8 | A plays range longer than 31 days, a daily range longer than 366 days, or a malformed filter. The CLI refuses most of these before sending anything (exit 2). |
rate_limited | 7 | The playback export budget of 30 requests a minute per project is spent. Wait for retry_after_seconds. |
Billing: each JSON page is one billed API request, so --all bills one request per page. A CSV export is one billed request for its whole range. Every plays request and every CSV export also spends the playback export budget.
playback plays#
Read per-play records, one row per visible start.
playback plays [--from TIME] [--to TIME] [--screen-id ID] [--media-id ID] [--tag TAG] [--cursor CURSOR] [--limit N] [--all]
playback plays --format csv [--output FILE] [--from TIME] [--to TIME] [--screen-id ID] [--media-id ID] [--tag TAG]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--from TIME | string | no | 24 hours before --to | Inclusive bound on received_at: an RFC 3339 instant with seconds and an offset, now, or an age such as 7d, 12h, or 30m. |
--to TIME | string | no | now | Exclusive bound, in the same forms. The range is at most 31 days. |
--screen-id ID | string | no | every screen | One scr_ screen. |
--media-id ID | string | no | every object | One med_ media object. |
--tag TAG | string | no | every screen | A tag the screen carried when the play was received. |
--cursor CURSOR | string | no | the start of the range | The next_cursor of the previous page, with the same filters. |
--limit N | integer | no | 200 | Rows per JSON page, at most 1000. Not with --format csv. |
--all | boolean | no | off | Follow the cursor to the end of the range, 1000 rows per page unless --limit says otherwise, for at most 50 pages. |
--format csv | string | no | JSON | Stream the whole range as CSV. |
--output FILE | path | no | ./playback-plays.csv | CSV target, or - for stdout only. |
screenrig playback plays --from 7d --tag Lobby --all
screenrig playback plays --format csv --from 2026-09-01T00:00:00Z --to 2026-10-01T00:00:00Z --output september-plays.csvThe CLI normalizes both bounds to UTC and sends them on every request; data.from and data.to echo them. The newest 5 seconds are held back until they settle, so a play appears about 5 seconds after it is received, and data.to reports that effective end. JSON returns one page in data.items with data.next_cursor, and data.next.argv is the exact command for the next page. --all stops early, with data.next and a warning, at the page cap (playback_plays_truncated) or when the export budget is spent (playback_export_rate_limited), rather than spending requests into a refusal.
The CSV is RFC 4180 with the fixed header row screen_id,playlist_id,page_id,primitive_id,media_id,primitive,started_at,received_at,manifest_revision,playlist_revision, times in RFC 3339 UTC. primitive_id and started_at are empty unless the Player reported them. A cell that begins with =, +, -, @, tab, or carriage return carries a leading ' so spreadsheets do not evaluate it. The CLI writes through a private temporary file in the same directory and replaces an existing file only after the stream ends cleanly. --timeout is the no-progress limit for the stream, 60000 ms without a byte by default, not a limit on its total duration. The envelope reports path, bytes, rows, sha256, and last_received_at; the CSV never enters the envelope.
If a stream fails partway, the command exits non-zero and never writes the target file. The complete rows already received stay in FILE.partial, and error.next exports the rest of the range from the last received_at into an unused FILE-rest.csv. Rows received at that exact instant appear in both, so drop the repeats when joining them.
playback list#
Read daily playback totals for this project, newest days first.
playback list [--screen-id ID] [--media-id ID] [--day YYYY-MM-DD | --day-from YYYY-MM-DD --day-to YYYY-MM-DD] [--format csv] [--output FILE]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--screen-id ID | string | no | every screen | Must start with scr_. |
--media-id ID | string | no | every object | Must start with med_. |
--day YYYY-MM-DD | string | no | every day | One UTC calendar day. |
--day-from YYYY-MM-DD | string | no | none | Start of an inclusive UTC day range, with --day-to, at most 366 days. |
--day-to YYYY-MM-DD | string | no | none | End of that range. |
--format csv | string | no | JSON | Export as CSV. With no range, the 31 days up to today. |
--output FILE | path | no | ./playback-aggregates.csv | CSV target, or - for stdout only. |
screenrig playback list --screen-id scr_01EXAMPLESCREEN00000000 --day 2026-08-14
screenrig playback list --format csv --day-from 2026-09-01 --day-to 2026-09-30One row per screen, media object, and UTC day. The filters select the caller's own rows. The CSV header row is screen_id,media_id,filename,primitive,day,play_count,last_page_id,last_manifest_revision,first_started_at,last_started_at. A failed daily export keeps nothing; rerun it.
Support conversations
Every plan, including Standard, includes project support chats through the CLI and the dashboard's mustard Support button at the bottom right. Conversations retain their history. The dashboard highlights unread staff replies and renders Markdown, including clickable links, lists and code. Press Enter to send or Shift+Enter for a new line. Staff replies show their first name and Slack avatar when available, with ScreenRIG staff and the screenRIG logo as the fallback; customers use a generic person avatar.
Use the documentation and your operating agent to diagnose and resolve issues. Use feedback bug or feedback feature to send reports.
support status
support submit (--body TEXT | --body-file FILE) [--conversation-id ID] [--human-requested]
support history [--before ID]
support history --conversation-id ID [--after SEQUENCE]
support follow [--conversation-id ID] [--after SEQUENCE] [--timeout MS]
support read --conversation-id ID --sequence SEQUENCE
support close --conversation-id IDStart with support status, which reports availability and staffed hours. support submit accepts one message of 1–4000 characters. Omit --conversation-id to start a conversation; use the returned data.conversation.id for follow-ups. Keep the same idempotency key when retrying an uncertain submission.
History pages contain up to 100 rows. Pass a conversation page's data.next as --before for older conversations, or the last message sequence as --after for newer messages. support follow streams replies as SSE, resumes from the last project sequence and suppresses replay. A conversation filter selects which replies it prints while advancing that project cursor. support read acknowledges staff replies through the sequence you have read. End a resolved conversation with support close or the dashboard’s End conversation button. Closing retains its history; start a new conversation for more help.
screenrig support submit --body "How do I assign a playlist? Here are the checks I ran."
screenrig support follow --conversation-id sc_CONVERSATION --after 0 --timeout 60000
screenrig support submit --conversation-id sc_CONVERSATION --body "Please ask a human." --human-requestedSupport hours are 09:00–17:00 Monday–Friday, Vancouver time, excluding B.C. statutory holidays. Luna attempts an answer outside those hours or after 30 minutes without a staff reply. Asking for a human pauses AI in that conversation. Keep credentials out of support messages.
Feedback
Bug reports and feature requests are project-scoped and immutable. The kind comes from the command, never from a body field, and a correction is a new submission rather than an edit.
feedback bug#
File a bug report against screenRIG.
feedback bug <title> (--body TEXT | --body-file FILE) [--command "GROUP ACTION"] [--no-context]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<title> | string | yes | none | At most 120 characters. |
--body TEXT | string | one of the two | none | Inline body, at most 4000 characters. |
--body-file FILE | path | one of the two | none | Read the body from a file, which suits a multi-line report. |
--command | string | no | none | The command the report is about, as a command path only: up to four lowercase words such as media upload. It is never taken from the command line you ran. |
--no-context | boolean | no | off | Omit the diagnostic envelope, which otherwise holds the CLI version, the optional command path, and the platform. |
screenrig feedback bug "Playlist stalls after assignment" --body-file ./report.md --command "screen assign"The server rejects, rather than redacts, text matching credential material, and returns field-level guidance so you can rewrite and resend. An exact retry under the same idempotency key returns the original submission for twenty-four hours.
feedback feature#
File a feature request.
feedback feature <title> (--body TEXT | --body-file FILE) [--command "GROUP ACTION"] [--no-context]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<title> | string | yes | none | At most 120 characters. |
--body TEXT | string | one of the two | none | Inline body, at most 4000 characters. |
--body-file FILE | path | one of the two | none | Read the body from a file. |
--command | string | no | none | The command the request is about, as a command path only. |
--no-context | boolean | no | off | Omit the diagnostic envelope. |
screenrig feedback feature "Add a dry-run flag" --body "Preview a change first."feedback list#
List this project's own submissions.
feedback list [--kind bug|feature]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--kind | string | no | both kinds, newest first | bug or feature. Restrict the listing to one kind. |
screenrig feedback list --kind bugAdvertising
Buy advertising in a network that invited you, or sell ad breaks on screens you own. Every command reads screenrig project capabilities first: buying needs the advertiser capability, selling needs owned screens. Money is reported as decimal mcr (millicredit) strings. Run screenrig ads COMMAND --help --json for every flag. The buying and selling workflow is in Start; rates and fees are on Pricing.
ads networks#
Buyer: list the networks this project was invited into, and inspect one network's permitted inventory and slots.
ads networks list
ads networks show <seller-project-id>screenrig ads networks listads network#
Seller: inspect or create this project's own network and set its default rate.
ads network show
ads network create --name NAME
ads network rate --rate-mcr-per-15s MCR --expect-rev REVISIONscreenrig ads network rate --rate-mcr-per-15s 1000 --expect-rev 3The rate is per 15 seconds of completed playback. A changed effective rate pauses affected campaigns until each buyer accepts a fresh quote.
ads inventory#
Seller: opt an owned screen in or out of advertising and describe it to invited buyers.
ads inventory list
ads inventory update <screen-id> [--enabled | --disabled] [--site-name NAME]
[--city CITY] [--region REGION] [--venue-type TYPE] [--audience-tags TOKENS]
[--placement TEXT] [--public-description TEXT]
[--rate-mcr-per-15s MCR | --clear-rate] [--expect-rev REVISION]screenrig ads inventory update scr_LOBBY --enabled --venue-type cafeOmitted flags keep their current values. A new inventory row needs --enabled or --disabled.
ads slots#
Seller: define reusable ad slots. A slot changes nothing on the glass until its adslot page is placed in a playlist.
ads slots list
ads slots create --name NAME --accepted-media image,video
[--max-image-duration-ms MS] [--max-video-duration-ms MS]
[--rate-mcr-per-15s MCR] [--enabled | --disabled]
ads slots update <slot-id> --expect-rev REVISION [same fields]screenrig ads slots create --name "Lobby break" --accepted-media image,videoAdvertising invitations#
Use the shared invitations commands above:
screenrig invitations create --kind ad-buyer --email owner@bakery.example --screen-id scr_LOBBY --policy review_required
screenrig invitations list --kind ad-buyer
screenrig invitations revoke ID
The invited person accepts in the dashboard. A signed-in person selects their buying project and confirms with a fresh passkey proof. A person without a login creates one and an advertiser-only project during acceptance.
ads memberships#
Seller: change or revoke an admitted buyer's review policy and inventory scope.
ads memberships list
ads memberships update <membership-id> --expect-rev REVISION
[--policy trusted|review_required] [--screen-id IDS] [--slot-id IDS]
ads memberships revoke <membership-id>screenrig ads memberships listRevocation stops new ad plays and keeps financial history.
ads creatives#
Buyer: bind ready owned media to an immutable creative with approved copy.
ads creatives list
ads creatives create --media-id ID --copy TEXT
ads creatives show <creative-id>screenrig ads creatives create --media-id med_123 --copy "Fresh bread daily"ads campaigns#
Buyer: draft a campaign, price it, and activate it by accepting the quote.
ads campaigns list | show <campaign-id>
ads campaigns create <file>
ads campaigns update <campaign-id> <file> --expect-rev REVISION
ads campaigns preview <campaign-id> [--expect-rev REVISION]
ads campaigns activate <campaign-id> --quote-id ID --expect-rev REVISION
ads campaigns pause | resume <campaign-id> --expect-rev REVISION
ads campaigns accept-rates <campaign-id> --quote-id ID --expect-rev REVISIONscreenrig ads campaigns preview cmp_123A draft reserves nothing. preview returns a time-limited quote and spends nothing; activate requires that exact quote. After a seller rate change, only accept-rates with a fresh quote resumes the campaign.
ads reviews#
Seller: approve or reject exact submitted creative versions from buyers on review_required.
ads reviews list
ads reviews show <review-id>
ads reviews approve <review-id>
ads reviews reject <review-id> --reason TEXTscreenrig ads reviews listads reports#
Read spend as a buyer or delivery as a seller.
ads reports spend --campaign-id ID
ads reports delivery --from TIMESTAMP --to TIMESTAMPscreenrig ads reports delivery --from 2026-09-01T00:00:00Z --to 2026-10-01T00:00:00ZDelivery reports gross, serving fee and net for the period. A buyer report never shows a seller's other income.
Prepare, publish, and edit playlists
For ready images and videos, prepare a full-screen playlist in playback order:
screenrig playlist init med_POSTER med_VIDEO --name "Lobby loop" --screen-id scr_LOBBY --output lobby.json
screenrig playlist preview lobby.json --output preview --contact-sheet
screenrig screen publish scr_LOBBY lobby.jsonInspect the document and preview before publishing. playlist init accepts local image/video files, ready med_ IDs, pinned rel_ application releases, and HTTPS iframe URLs in playback order, including mixed inputs:
screenrig playlist init ./poster.png med_VIDEO rel_APP https://example.com --name Lobby --screen-id scr_LOBBY --output lobby.jsonLocal files upload through the normal media path and wait for readiness. This requires ffmpeg and ffprobe unless --no-transcode is supplied. Preparation writes the local document and may upload media; it neither creates a remote playlist nor assigns a screen. Existing media must be ready. HTTPS URLs must contain no username or password and are not fetched during preparation.
For retryable file preparation, choose --idempotency-key on the first attempt and reuse it with identical files and input order within the server replay window. Each file occurrence gets its own upload keys. Without an explicit key, a new invocation gets new keys. Uploaded media survives a later preparation failure; inspect media list and reuse those IDs when reconciling a partial result.
Each input becomes one full-screen page with a black background and a 200 ms crossfade. Images and videos use --fit contain|cover|fill (default contain). Videos are muted, do not loop, and advance on completion. Other pages use --duration-ms (default 8000). Applications and iframes use fill; application releases are pinned with timed advancement and no controller privileges. Edit the document for application-controlled advancement. The static preview represents applications and iframes with placeholders; verify release readiness and actual playback through the server and intended Player. Framing is a property of the target page, not a publish-time check.
Supply --screen-id to read the target dimensions and revision. The result includes data.screen_id, data.screen_revision, data.preview.argv, and data.publish.argv. Pass these argument arrays to the bundled screenrig launcher without shell splitting: they preserve paths, config, and API origin. Inspect the preview before publishing. Add --expect-rev explicitly to guard against later screen changes. Target metadata stays outside the authored document.
To override the canvas, supply both --target-width and --target-height. These also work without a screen, in which case no publish arguments are returned. Unknown or multiple reported surfaces require explicit dimensions. Output files are exclusive by default; use --overwrite to replace an existing authoring file atomically. Parent directories are created automatically. URL and release-ID inputs with explicit dimensions work without login.
screen publish <screen-id> <file> creates a new playlist, assigns it with an optional screen revision guard, and reads back the assignment. It does not update an existing playlist by name. Display names may repeat; use the playlist ID for an explicit playlist update. Its JSON result reports playlist_id, playlist_revision, screen_id, screen_revision, assignment_verified, and playback_verified. Assignment verification does not prove playback; request and inspect a screenshot and relevant playback evidence separately.
Publishing saves a private local journal automatically. After an ambiguous failure, repeat the identical command and input with the same config to resume. If playlist creation succeeded before assignment failed, the error identifies the created playlist. Do not delete it or start another create to recover. A revision conflict requires inspecting the screen and reconciling the intended assignment; an already created playlist can be assigned explicitly with screen assign. Publishing never silently refreshes the expected revision. Unfinished recovery stops after the 24-hour server idempotency window; inspect and reconcile before making more writes.
To edit an existing playlist:
screenrig playlist show pl_EXISTING --output lobby.json
screenrig playlist update pl_EXISTING lobby.jsonplaylist show --output writes the editable {name, pages} document and returns its path, playlist_id, and source revision on stdout. Optionally pass that revision with --expect-rev to guard the update. It preserves dynamic selectors, schedules, motion, and pinned application releases, removing server-derived media and timing fields. Comments remain separate. Plain playlist show is an inspection response; --editable without --output returns data.document, data.playlist_id, and data.revision. Updating a playlist affects every screen assigned to it.
K/V and comment writes accept --file - for stdin. Use playlist show ID --editable --human for the editable document on stdout. Application and playlist display names may repeat; use resource IDs for updates.
Replace one application release
After a successful app update, take data.application.release_id and identify the exact playlist, page, and application primitive to change. Preview the replacement:
screenrig playlist replace-release pl_EXISTING --page board-page --primitive board --release-id rel_NEWThis is a read-only impact review, not a rendered preview. Inspect data.previous_release_id, data.release_id, and data.affected_screens, including archived assignments. Apply directly with --apply. To opt into this impact check, also supply --expect-impact with the preview’s data.impact:
screenrig playlist replace-release pl_EXISTING --page board-page --primitive board --release-id rel_NEW --applyOther primitives and settings are preserved. The update affects every screen assigned to this shared playlist; archived screens retain the new pin for later use. When --expect-impact is supplied, a changed replacement, revision, or observed screen impact requires a fresh preview. Direct apply skips the screen survey; affected_screens is omitted. An already pinned release succeeds without writing. Assignments may change after the snapshot: the server checks the playlist revision atomically only when --expect-rev is supplied. The server validates release availability and ownership on apply. Existing pins remain unchanged until apply; afterward, verify manifest revisions and playback on the affected screens.
Playlist validate, create, update, preview, and screen publish accept - as their input file to read stdin. JSON envelopes are never written into authored files. --expect-rev is the preferred revision spelling; --if-match remains a compatible alias. Supply only one. Omit the flag to write the current resource without a revision precondition. A supplied stale revision still returns revision_conflict.
Generation accepts either --prompt TEXT or --prompt-file FILE, including --prompt-file - for stdin. The file is the complete prompt, with no trimming; the existing 4000-character limit applies. Prompts are excluded from diagnostics.
You should pay per byte, not per screen
Storage and transfers are metered across the project. Each device downloads its own copy and can replay cached media. See pricing for included usage, rates, and a worked fleet example. See pricing →
Keep the path short
The bundled CLI uses api.screenrig.ai; Players use play.screenrig.ai; the reporting plane uses screenrig.ai/dashboard; releases use isolated origins under apps.screenrig.ai. Native Player packages stay private and server-mediated. The internal transport reference remains available for transport tooling.
Tag filters and playback batches
Media carries up to 32 unique tags, each 1–32 letters or digits. Replace the whole set with screenrig media update med_ID --tags Summer,Drinks. --tag Summer replaces it with one tag; --clear-tag clears every tag. media list --tag Summer matches membership anywhere in the set. Tags are case-sensitive and are never authorization.
A dynamic selector can use one tag or a tags filter object:
{
"by": "tag",
"tags": { "all": ["Summer", "Drinks"], "any": ["Lobby", "Patio"], "none": ["Expired"] },
"order": "random",
"one_at_a_time": false,
"batch_size": 3
}
all requires every listed tag, any requires at least one, and none excludes any listed tag. Present groups combine with AND; omitted groups impose no condition. Supply at least one nonempty group. Each group accepts up to 32 unique tags. tag and tags are mutually exclusive. A none-only filter selects all ready media of that primitive except the excluded tags. Image selectors select images and video selectors select videos.
The candidate pool contains up to 1,024 ready media per selector, with 1,024 media references across the complete loop snapshot. A larger match returns an error; narrow the filters. batch_size is a separate limit of 1–1,024 items per page visit. On a media_end page, one_at_a_time: false plays the next batch in sequence; omitting batch_size plays the remaining pass. one_at_a_time: true plays one item per appearance and allows only an omitted batch size or batch_size: 1. Duration and application pages require one_at_a_time: true. Images on media_end also require dwell_ms; videos cannot loop on that page.
The Player resolves every dynamic media primitive in one request at playlist startup and at each loop boundary. Membership is frozen during the loop. Uploads, removals, and tagging changes do not regenerate the manifest. At the next refresh, removed or newly excluded items leave the pending queue; new matches join when the current pass finishes. A final batch may contain fewer than the requested number; it does not refill from the next pass. Filename order sorts by filename, then media ID. Random order shuffles once per complete pass, preserving the remaining order across loop refreshes. The cursor is local to the screen, playlist, page, and primitive, persists across restart, and resets when the filter, order, or batch policy changes. Offline playback uses the last successful snapshot. Empty matches skip the primitive and any resulting empty page.
Save the selector object above as selector.json, then inspect its current candidate catalog before publishing:
screenrig media selector-preview selector.json --primitive video
Preview returns matched_count, filename-ordered candidates with media IDs and tags, and the normalized selector policy. With random order it shows the pool and shuffle policy; it does not choose the Player's shuffle, advance a cursor, create a playback grant, or change a manifest. The API is POST /api/selectors/preview with { "primitive": "video", "selector": ... }. It uses the authenticated project and the same matching rules as playback. Catalog changes after preview are resolved at playback's next loop boundary.
Static bundle export freezes dynamic matches as exact IDs. It preserves one-at-a-time or complete-pass playback; a bounded runtime sequence using batch_size with one_at_a_time: false requires removing that field or choosing explicit IDs before export. This prevents silently losing a runtime batch cursor in a static bundle.
Comments
A comment is the agent's own JSON object attached to a screen, a playlist, or one playlist page. Compact UTF-8 of that object is at most 1 KiB, and the value must be an object. screenRIG never reads it, never sends it to Players, and never treats it as authorization. It does not appear on the runtime manifest and does not bump a revision, so these commands take no
--expect-rev: last write wins.comment show#Read the comment object on a screen, a playlist, or one playlist page.
<id>scr_orpl_id, matching the target word.--page PAGE_IDAn unset comment reads as
{ "comments": null }.screen showandplaylist showincludecommentswhen the server sends it.comment set#Write the comment object.
<id>scr_orpl_id.--json-value JSON--file FILE--page PAGE_IDPlaylist create and update cannot set comments; that is what these commands are for.
comment delete#Remove the comment object.
<id>scr_orpl_id.--page PAGE_ID