Fleet links
A fleet link is one Player URL that you put in a device manager, such as a kiosk app in the Google Admin console. Every device that opens it becomes its own screen in this project, with no pairing code. An organization has at most 50 fleet links across its projects.
Each new screen gets the link's tags, fields and playlist, and a label from the device: its administrator asset ID, then its location, else the next default label. A device keeps its one screen across reloads and relaunches. After a powerwash or reinstall, a device that reports the serial or DUID of a screen a fleet link of this project created recovers that screen instead of creating another, once its old key has been silent for ten minutes. screen show returns fleet_link_id for a screen a fleet link created, and --human prints Created by fleet link: flk_…. The id stays after the link is deleted.
A link can end at a time you choose: --expires-at stops it creating and recovering screens from then on, as a disabled link does, and a device that opens it shows its pairing code. The screens it created keep playing. fleet-links show --android-provisioning prints the payload that provisions a factory-reset Android device as the device owner; see Android device owner by QR code.
The URL, https://play.screenrig.ai/player/#fleet=srfl_…, is a credential that creates screens. Give it only to the device manager. The secret travels in the URL fragment, which browsers never send to a server. A Read only credential receives data.url empty with url_redacted: true, and human output then says URL: withheld from a Read only credential. Otherwise human output prints the URL under a Sensitive fleet link URL label, and fleet-links list --human never prints it. The CLI checks a returned URL before printing it: HTTPS, the /player/ path, and the secret only in #fleet=. It withholds a URL that fails the check, with the warning fleet_link_url_withheld, and it keeps fleet link answers out of its operation log.
| Problem code | Exit | Meaning |
|---|---|---|
fleet_link_limit_reached | 5 | The organization already has 50 fleet links. Nothing was created. Delete one first. |
invalid_request | 8 | A field was refused, such as a --playlist-id that names no playlist in this project. error.next is playlist list for an unknown playlist. |
not_found | 4 | No fleet link has this id in the project. |
revision_conflict | 6 | The --expect-rev revision was stale. Run fleet-links show, then retry. |
fleet_link_secret_unavailable | 3 | fleet-links show --android-provisioning received no usable URL, as for a Read only credential. A Manage sign-in receives it. |
fleet-links create#
Create a fleet link and receive its Player URL.
fleet-links create --name NAME [--tag TAG]... [--field NAME=VALUE]... [--playlist-id ID] [--max-screens N]
[--expires-at TIME] [--disabled]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--name NAME | string | yes | none | The link's name, 1 to 80 characters without control characters. |
--tag TAG | string | no | no tags | A tag for each new screen, 1 to 32 letters or digits. Repeat it, or list tags comma-separated; at most 16. |
--field NAME=VALUE | string | no | no fields | A field for each new screen, which cards read as {screen.field.NAME}. NAME is 1 to 32 lower case letters, digits or underscores and VALUE 1 to 200 characters. Repeat it; at most 32. |
--playlist-id ID | string | no | none | The pl_ playlist assigned to each new screen. |
--max-screens N | integer | no | no cap | The most screens the link creates, 1 to 10000. |
--expires-at TIME | string | no | never | When the link stops creating and recovering screens: an RFC 3339 time with seconds and a zone, such as 2026-12-31T23:59:59Z. A time already past expires the link at once. |
--disabled | boolean | no | off | Create it disabled. It creates no screens until fleet-links update --enable. |
screenrig fleet-links create --name "Lobby displays" --tag Lobby --tag Floor2 --playlist-id pl_01EXAMPLEPLAYLIST000000screenrig fleet-links create --name "Trade show" --field booth=A12 --expires-at 2026-12-31T23:59:59ZThe envelope is the fleet link with its Player URL in data.url. Put that URL in the device manager. 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 link instead of creating another.
fleet-links list#
List this project's fleet links, oldest first.
fleet-links listscreenrig fleet-links listA Read only credential receives each data.items[].url empty with url_redacted: true. --human prints ID, LABEL, ENABLED, SCREENS, TAGS, and LAST_USED, adds EXPIRES while a listed link has an expiry and marks one past it (expired), and never prints the URL. SCREENS is the number of screens the link created, or created/max with --max-screens; recoveries are not counted.
fleet-links show#
Inspect a fleet link and receive its Player URL.
fleet-links show <id> [--android-provisioning]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The flk_ fleet link id. |
--android-provisioning | boolean | no | off | Print the Android device owner provisioning JSON for this link in place of its details, to encode as a QR code for a factory-reset device. It carries the link's secret, so it needs a Manage sign-in. |
screenrig fleet-links show flk_01EXAMPLELINK0000screenrig fleet-links show flk_01EXAMPLELINK0000 --android-provisioningscreens_created counts the screens the link created, and last_used_at is the last time it created or recovered one. expires_at is present when the link has an expiry, and human output marks it (expired) once it has passed.
With --android-provisioning, data.id and data.provisioning replace the link's details. The JSON carries the link's secret: show its QR code only to the person at the device. A Read only credential receives no URL to build it from and gets fleet_link_secret_unavailable (exit 3). Android device owner by QR code lists the payload.
fleet-links update#
Change what a fleet link gives the screens it creates from now on.
fleet-links update <id> [--name NAME] [--enable | --disable] [--tag TAG]... [--clear-tags]
[--field NAME=VALUE]... [--clear-fields]
[--playlist-id ID | --clear-playlist] [--max-screens N | --clear-max-screens]
[--expires-at TIME | --clear-expires-at] [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The flk_ fleet link id. |
--name NAME | string | at least one change | unchanged | 1 to 80 characters. |
--enable | boolean | at least one change | off | The link creates and recovers screens again. |
--disable | boolean | at least one change | off | The link creates and recovers no screens; a device opening it shows its pairing code instead. |
--tag TAG | string | at least one change | unchanged | Replaces the whole tag set. Repeat it, or list tags comma-separated; at most 16. |
--clear-tags | boolean | at least one change | off | New screens get no tags. |
--field NAME=VALUE | string | at least one change | unchanged | Replaces the whole field set, as in fleet-links create. Repeat it; at most 32. |
--clear-fields | boolean | at least one change | off | New screens get no fields. |
--playlist-id ID | string | at least one change | unchanged | The pl_ playlist for new screens. |
--clear-playlist | boolean | at least one change | off | New screens get no playlist. |
--max-screens N | integer | at least one change | unchanged | The most screens the link creates in all, 1 to 10000. |
--clear-max-screens | boolean | at least one change | off | Remove the cap. |
--expires-at TIME | string | at least one change | unchanged | Set or move the expiry: an RFC 3339 time with seconds and a zone. A time already past expires the link at once. |
--clear-expires-at | boolean | at least one change | off | Remove the expiry; the link never expires. |
--expect-rev REVISION | string | no | none | Optional revision precondition. |
screenrig fleet-links update flk_01EXAMPLELINK0000 --playlist-id pl_01EXAMPLEPLAYLIST000000update changes only the fields you pass, and only for screens the link creates from now on. Screens it already created keep their tags, playlist, and label. An expired link behaves as a disabled one until its expiry moves or is cleared. The URL is unchanged; use rotate to replace it.
fleet-links rotate#
Replace a fleet link's URL and receive the new one.
fleet-links rotate <id> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The flk_ fleet link id. |
--expect-rev REVISION | string | no | none | Optional revision precondition. |
screenrig fleet-links rotate flk_01EXAMPLELINK0000The envelope carries the new URL in data.url. The old URL stops working at once: a device that opens it shows its pairing code. Screens the link already created keep playing, because a paired Player never presents the link again. Put the new URL in the device manager. After an interrupted or ambiguous rotation, rerun the identical command: it reuses the saved idempotency key, so the server returns the rotation that already happened instead of rotating again.
fleet-links delete#
Delete a fleet link. Its URL stops working at once, and the screens it created stay.
fleet-links delete <id> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The flk_ fleet link id. |
--expect-rev REVISION | string | no | none | Optional revision precondition. |
screenrig fleet-links delete flk_01EXAMPLELINK0000Screens the link created keep their playlists and tags.
Migrate
migrate moves your previous signage platform's media, playlists, layouts, schedules and screens into the project. It runs in the CLI on your machine: the platform's key is read from a --credentials FILE of NAME=VALUE lines, goes only to that platform, and is never stored, printed, logged or sent to screenRIG, which sees ordinary media, playlist, fleet link, campaign and screen writes. migrate vendors lists the platforms it reads, and the files fallback reads a folder or .zip of media from any other platform.
Run the steps in order: migrate vendors (what a platform takes), migrate scan (read the account and report what can and cannot come over; it needs no screenRIG sign-in), migrate apply --dry-run (the plan), migrate apply (needs Manage access), then migrate pair for each display.
--dir DIR is the migration directory, ./screenrig-migration-VENDOR by default, created with mode 0700. It holds scan.json (the scan and its report), map.json (which object each old item became, bound to this API URL, project, vendor and source account) and staging/ (downloads in progress). apply and pair use the one screenrig-migration-* directory in the working directory, or take --dir; one of them runs in a directory at a time.
| Problem code | Exit | Meaning |
|---|---|---|
migration_dir_mismatch | 2 | The directory was made for another project, API URL, vendor or source account. Use another --dir. |
vendor_refused | 3 | The platform refused the key. Make a read-only key (migrate vendors VENDOR shows how), fix the --credentials file and run the same command again. |
vendor_not_found | 4 | The platform answered 404. --url is usually wrong. |
vendor_rate_limited | 7 | The platform is rate limiting the key. Run the command again later. |
vendor_file_gone | 8 | A media file address expired or is protected. migrate scan again refreshes it, and --files supplies a file that still fails. |
vendor_file_stalled | 8 | A media download went silent. Run apply again. |
vendor_bad_response | 9 | The platform sent an answer the CLI cannot use. |
vendor_unavailable | 9 or 10 | The platform could not be reached or kept failing. |
migrate vendors#
List the platforms migrate reads, what each takes and how to make its read-only key.
migrate vendors [VENDOR]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
VENDOR | string | no | every platform | One platform's id. An id it does not list is usage_error (exit 2) naming the ids that exist. |
screenrig migrate vendors
screenrig migrate vendors filesIt works offline and needs no sign-in. data.vendors holds, for each platform, its id and name; inputs, with the flags it takes (each with flag and required, and values for a region) and the credentials lines its key file holds (name, secret, required and help); connect, the steps that make a read-only key; maps, what comes over; skips, what does not; docs; and example, a working migrate scan command. Platforms are sorted by id with files last. files takes a folder or .zip and an optional time zone, and needs no key.
migrate scan#
Read a signage account and report what can and cannot come over. It writes scan.json and sends nothing to screenRIG.
migrate scan --from VENDOR [--url ADDRESS] [--region REGION] [--timezone ZONE] [--files PATH]
[--credentials FILE] [--dir DIR]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--from VENDOR | string | yes | none | The platform to read: an id from migrate vendors, or files. |
--url ADDRESS | string | per platform | none | The address of a self-hosted platform. |
--region REGION | string | per platform | none | The region of a hosted account, for a platform that has regions. |
--timezone ZONE | string | no | none | An IANA time zone name such as America/Toronto, set on each screen whose old platform names none. Without it a screen follows the zone its Player reports. |
--files PATH | path | per platform | none | A folder or .zip. With --from files it holds the media; with another platform it holds the originals that platform's API cannot hand out, matched to its media by file name. |
--credentials FILE | path | per platform | none | The key file: NAME=VALUE lines with the names migrate vendors VENDOR lists. Blank lines and # comments are skipped, and a value may sit in one pair of quotes. A required name missing from the file is usage_error (exit 2) naming it. |
--dir DIR | path | no | ./screenrig-migration-VENDOR | The migration directory. |
screenrig migrate scan --from files --files ./signage-media
screenrig migrate scan --from files --files ./signage-media.zip --dir ./old-signage --timezone America/TorontoA flag the platform does not take, or one it needs and lacks, is usage_error (exit 2) naming what the platform takes. No flag takes the key itself. A new scan replaces scan.json and keeps map.json, and it exits 0 when it completes, even when some items cannot come over.
data is the report: vendor, label, scanned_at, found and importable (counts of media, playlists, layouts, schedules, groups and screens), not_imported (each with kind, source_id, name, a reason and usually a next step), lossy (each with kind, source_id, name and the change that happens on the way), notes, dir and scan_file. A file the platform hands out downloads at apply. A file it does not, or hands out only as a re-encoded copy, is matched by name in --files; with no match, or several, it is in not_imported or lossy with the step that fixes it.
--from files needs no key. It reads each image, video and audio file by its contents, not its extension. An optional playlists.csv at the root has the columns playlist, file (a path or a unique file name), duration (seconds) and order; without it each folder that holds media becomes a playlist in natural file-name order, and the files at the root form one named after the folder or zip. An optional screens.csv has name, group (several separated by ;), playlist and timezone, and each row becomes a waiting screen with its groups as tags. Other files and encrypted zip entries are in not_imported. A files scan has no schedules: create them with campaign create once the playlists exist.
migrate apply#
Import what the scan found: media, playlists and layouts, waiting screens and campaigns. It is safe to rerun.
migrate apply [--dir DIR] [--dry-run] [--only KINDS] [--folder ID] [--canvas WIDTHxHEIGHT]
[--credentials FILE] [--concurrency N] [--no-transcode]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--dir DIR | path | no | the one ./screenrig-migration-* directory | The migration directory that holds scan.json. |
--dry-run | boolean | no | off | Read the project and print the plan. It writes nothing and needs no key. |
--only KINDS | string | no | all four | Comma-separated, each at most once: media, playlists, screens, schedules. |
--folder ID | string | no | a new folder named Imported from and the platform's name | An existing media folder (mfd_…) for the imported media. |
--canvas WIDTHxHEIGHT | string | no | 1920x1080 | The canvas of plain playlists, each side at least 16 pixels. A layout keeps its own size. |
--credentials FILE | path | no | none | The key file, read only when a media file is downloaded from the platform. |
--concurrency N | integer | no | 4 | Media imported at once, 1 to 8. |
--no-transcode | boolean | no | off | Upload the source bytes unchanged, as media upload does. |
screenrig migrate apply --dry-run
screenrig migrate apply --dir ./screenrig-migration-files
screenrig migrate apply --only media,playlists --concurrency 2apply imports in order: media into one folder, playlists and layouts, one waiting screen per old screen, and a campaign per schedule that campaigns can express. It reads the platform again only to download media files. A playlist has one page per item: images and videos fill the canvas, a web address becomes an iframe page, audio becomes the soundtrack and a nested playlist is inlined. A layout becomes a playlist on its own canvas with one page per item of its main zone. A schedule that repeats by the month, year, hour or minute, or on a numbered weekday of the month, cannot become a campaign: scan lists it in not_imported.
--dry-run reads only the project and answers data.plan (for media, playlists, layouts, screens and schedules: create, reuse, recreate, waiting and fail counts), data.items, data.bytes to download, data.placeholders counts and data.project, the fleet links and campaigns in use against their maximums of 50 and 100. Progress goes to stderr as JSON lines (migrate_progress with phase, done and total), or as text with --human. A rerun reuses what still exists, makes again anything you deleted and never changes what it made. A real run answers data.applied (the same counts), data.waiting (items that wait on media or a playlist that is not imported, each with a reason), data.failed, data.placeholders (one entry per old screen with its source_id, name and state: linked, unlinked, paired, finished or waiting), data.tags and data.folder_id.
A group becomes a tag made of letters and digits, numbered when it collides with a tag another screen carries. A campaign targets the tags of the groups and screens its schedule names, with its dates, days, hours and priority. A campaign reads its dates and hours in each screen's own timezone and needs a default playlist (no_default_playlist): scan notes the screens that lack one, and screen assign sets it after pairing.
A waiting screen is a fleet link of one screen that carries the old name, the groups as tags and the playlist. An organization holds at most 50 fleet links across its projects; the rest are unlinked in data.placeholders and wait for a Player code (migrate pair). A Player that opens a link becomes a screen, and running apply again gives it the old name and time zone and deletes the link. apply never prints a fleet link URL: fleet-links show does.
A failing item keeps ok true with data.failed (each with key, name, code, status and detail) and the warning migration_items_failed; the items that depend on it wait, and the exit code follows the status of the last failure. A media file that fails alone (an expired address, an answer that is not a file, a download that goes silent) fails that media only; for an expired address run migrate scan again. A refusal that would repeat for every item (quota_exceeded, campaign_limit_reached, insufficient_access, pending_review, the platform refusing the key, a lost connection) ends the run with data.stopped (code, detail and next) and that problem's exit code. Fix the cause and run the same command again: it continues where it stopped.
migrate pair#
Pair a Player code to one waiting screen, under its old name, playlist, time zone and tags.
migrate pair <CODE...> --screen ID_OR_NAME [--dir DIR]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<CODE...> | string | yes | none | The code the Player shows, such as ABC 234: six characters from 23456789ABCDEFGHJKMNPQRSTUVWXYZ, in either case. Spaces and dashes between them are ignored, so it needs no quotes. |
--screen ID_OR_NAME | string | yes | none | The waiting screen: its source id or its exact old name. A name that two screens share lists their ids. |
--dir DIR | path | no | the one ./screenrig-migration-* directory | The migration directory. |
screenrig migrate pair ABC 234 --screen "Front desk"
screenrig migrate pair ABC234 --screen 3 --dir ./old-signageRun migrate apply first: pairing needs the map and the playlist it made. pair claims the code for the screen under its old name, sets its playlist, time zone and migrated_from field, sets its tags, deletes the waiting screen's link and records the result. data holds source_id, name, screen_id, steps (paired, updated, tagged, link deleted, recorded), next and, when this run claimed the code, public_url. Each step is recorded as it lands, so a rerun finishes what an interrupted pair left. A screen that is already paired is resource_conflict (exit 5) naming it. The Player finishes pairing the next time it contacts the service; screen show then reports state active.