From sheet to screens
- Start from the template. Save the template as CSV or XLSX, or use your own sheet with the same columns.
- Fill one row per schedule line. Name the screen (or a folder or a tag), the file, the start and the end.
- Import it. Drop the file on the Import dialog of the dashboard's Schedule tab, or run
screenrig schedule import FILE. - Correct the rows with errors. Each error names the row, the column and a code. Edit the cell in the dialog, or edit the sheet and import it again.
- Check the preview. It lists the levels the sheet makes, the ones it changes or removes, and the calendar of the screens it touches.
- Publish. All rows apply together or none does.
Steps 1 to 5 write nothing. A screen needs a time zone (screen set-timezone) and a default playlist (screen assign) before a row can schedule it, and a file must be an approved image or video.
The sheet
The first row names the columns. Each row after it is one schedule line. Blank rows are skipped. A sheet is a CSV file (UTF-8, comma or semicolon separated) or an XLSX workbook, and an XLSX workbook is read from its first sheet.
| Column | Also read as | What it holds |
|---|---|---|
target | screen, screen code, code, room, room id, screen name, screen id | The screens the row schedules. Required |
content | file, file name, media, asset, playlist | What plays. Required |
start | start date, from, begin | When the row begins |
end | end date, until, stop | When the row ends |
timezone | time zone, tz | The zone the row's times are written in |
days | weekdays, day | The days of the week the row plays |
daily start | window start, time from | The time of day each play window opens |
daily end | window end, time to | The time of day each play window closes |
priority | 0 to 100. Higher plays over lower. Default 0 | |
name | level, label | A name for the level the row makes |
display | displays, output | The video wall outputs the row plays on, such as 1 or 1,2 |
Headers match ignoring case, spaces and punctuation. A column nothing reads is ignored, and the preview lists it. A row needs a target, a content and at least one of start, end, days or a daily window.
Screen,File,Start,End,Timezone,Days,Daily start,Daily end,Priority,Name
LOBBY-01,welcome-spring.jpg,2026-11-05 18:00,2026-11-08 02:00,America/Los_Angeles,,,,0,
folder:Casino Floor,happy-hour.mp4,2026-11-05,2026-11-30,,Mon-Fri,16:00,19:00,5,Happy hour
tag:elevators,playlist:Holiday loop,2026-12-01 09:00,2026-12-26,,,,,0,
- Row 2 plays
welcome-spring.jpgon the screen with the codeLOBBY-01from 6 PM on 5 November until 2 AM on 8 November, Pacific time. - Row 3 plays
happy-hour.mp4on every screen in the folderCasino Floorand in the folders inside it, from 4 PM to 7 PM on weekdays, from 5 November to the end of 30 November. - Row 4 plays the playlist
Holiday loopon every screen with the tagelevators, from 9 AM on 1 December to the end of 25 December.
Targets
A target is one of these:
- A screen. A screen id, the value of any field of the screen (a room code, a store number), or the screen's name.
--code-fieldorcode_fieldlimits the match to one field, such ascodeorroom_id. - A folder.
folder:NAMEorfolder:Parent/Child. It reaches the folder and every folder inside it. - A tag.
tag:NAMEreaches every screen with the tag.
Several targets in one cell are separated by ; or |, as in LOBBY-01;LOBBY-02|tag:elevators.
Content
- A file. The name of a media file in the project, or the original file name of a converted upload. The file plays full screen. The first commit that uses a file makes the one-file playlist
Scheduled: FILE, and later imports reuse it. - A playlist.
playlist:NAMEor a playlist id. The playlist has an approved version and is not a template.
Times
- Dates and times.
2026-11-05 18:00,2026-11-05T18:00,11/5/2026 6:00 PMand spreadsheet date cells are read. A time with an offset such asZor+02:00is refused. - Each screen's own clock. Times are read on the clock of the screens the row reaches, so
18:00is 6 PM wherever the screen is. - Another zone. A
timezonecell (or the request'stimezone) says the row is written in that zone. The times convert to the screens' zone when all the row's screens share one zone. A row that reaches screens in several zones leavestimezoneempty and reads each screen's own clock. - A date alone as the end closes that day:
2026-11-30ends at the end of 30 November. A date alone as the start begins at midnight. - No end keeps the row playing until a later import removes it. The preview warns with
open_end. - Days.
Mon-Fri,Mon,Wed,Fri,Sat,Sun,weekdays,weekendsordaily. A row withdaysand no daily window plays all day on those days. - A daily window.
daily startanddaily endgive the hours of each day, such as16:00and19:00or4:00 PMand7:00 PM. Give both. An end at or before the start crosses midnight and belongs to the day it starts. Withoutdays, the window applies every day.
Preview and errors
The preview checks every row and every cell before anything is written. A row with an error is shown with its cells and each error names the column, a code and what to change.
| Code | Column | Meaning and fix |
|---|---|---|
required | target, content, daily start, daily end | The cell is empty, or only one of the daily window cells is filled. Fill it |
unknown_screen | target | No screen has that id, field value or name. Check the code, or the field code_field names |
ambiguous_screen | target | The value matches several screens. Use the screen id or a code that is unique |
unknown_folder | target | No screen folder has that name. Check the spelling |
ambiguous_folder | target | Several folders have that name. Write the path, such as folder:Hotel/Casino Floor |
unknown_tag | target | No screen has the tag. Check the spelling |
unknown_file | content | No file or playlist has that name. Upload the file, or correct the name |
ambiguous_file | content | Several files have that name. Rename one, or write playlist:NAME |
not_playable | content | The file is not an image or a video, the media waits for review, or the playlist is a template or has no approved version. Approve it, or choose another |
invalid_time | start, end, daily start, daily end | The cell is not a date and time, or it carries a zone offset. Write 2026-11-05 18:00 |
reversed_times | end | The end is not after the start. Correct either |
past_end | end | The end has passed. Move it later, or remove the row |
no_timing | start | The row says nothing about when. Give a start, an end, days or a daily window |
invalid_value | days, priority, display | The cell is not a list of days such as Mon-Fri, a whole number from 0 to 100, or display numbers such as 1,2 |
timezone | timezone | The zone is unknown, or the target holds screens in several zones. Use a zone such as America/Los_Angeles, or leave it empty |
screen_not_ready | target | A screen has no time zone or no default playlist. Run screen set-timezone or screen assign |
overlap | priority | Two rows hold at the same time on a screen at the same priority. Give one a different priority, or move a time |
limit | target | A screen would hold more than 32 levels of its own, or the project more than 100 shared levels. Merge rows, or target a folder or a tag |
Two warnings do not stop an import. open_end marks a row with no end. outranked marks a row that a shared level (a row for several screens, a folder or a tag) plays over on a screen.
A sheet with no rows, a file that is not UTF-8, more than 5,000 rows, a file over 8 MiB, or a commit that would write more than 400 screens' own schedules is refused with the reason.
Levels
Rows that agree on content, times, priority, name and display become one level. The target is not part of the comparison, so ten rows that play the same file at the same times on ten screens make one level.
- One screen makes an entry in that screen's own schedule.
- Several screens, a folder or a tag make one shared level, a campaign over that audience. A shared level outranks the own levels of a screen.
Grouping turns a venue's 200 screens into one campaign and not 200 entries, and a correction in the sheet changes one level. The name column names a shared level. Without one, it is called after the import, the content and the target, as in Spring promos: happy-hour.mp4 on Casino Floor.
Import again
The name of an import identifies it, and the default is import. Importing under the same name again:
- updates the levels it made, even when a row's dates changed;
- adds the levels of new rows;
- removes the levels whose rows are gone from the sheet;
- changes nothing when the sheet is the same.
Levels made by an import carry it in their id (a screen's entries start with imp_) and campaigns carry an import mark. Levels you made by hand are never touched. Use one name for one sheet, and a different name for another.
Run an import
Dashboard
Open a project's Schedule tab and choose Import. Drop a CSV or XLSX file. The dialog shows a table of the rows with each error beside its cell, and you edit the cell in place. It then shows the composed calendar of the screens the import changes. Publish commits it.
Command line
screenrig schedule template --output schedule.csv
screenrig schedule import schedule.csv --name "Spring promos" --timezone America/Los_Angeles
screenrig schedule import schedule.csv --name "Spring promos" --timezone America/Los_Angeles --commit
schedule import previews and writes nothing. --commit applies the sheet when every row is valid.
| Option | Meaning |
|---|---|
--name NAME | The import's name, 1 to 80 characters. The default is the file name without its extension |
--timezone ZONE | The zone the times are written in when a row has no timezone |
--code-field FIELD | Read a screen code only from this screen field |
--commit | Apply the sheet |
--format csv|xlsx | schedule template: the template's format |
--output FILE | schedule template: where to save it |
The command exits 0 for a valid sheet, 8 when rows have errors (nothing is written) and 5 when the project changed since the preview (run it again). FILE is a .csv, .xlsx or .json file, or - for standard input. A JSON file holds an array of row objects, or {"rows": [...]}, keyed by column name.
API
Send the sheet as JSON rows, or as the CSV or XLSX body with the settings as query parameters. Preview needs read access. Commit needs a credential with write access, which a person who can approve in the dashboard grants.
curl -s https://api.screenrig.ai/api/schedule/imports/preview \
--header "Authorization: Bearer $SCREENRIG_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"name": "Spring promos",
"timezone": "America/Los_Angeles",
"rows": [
{"target": "LOBBY-01", "content": "welcome-spring.jpg", "start": "2026-11-05 18:00", "end": "2026-11-08 02:00"},
{"target": "folder:Casino Floor", "content": "happy-hour.mp4", "start": "2026-11-05", "end": "2026-11-30",
"days": "Mon-Fri", "daily_start": "16:00", "daily_end": "19:00", "priority": 5, "name": "Happy hour"}
]
}'
The same sheet as a file:
curl -s "https://api.screenrig.ai/api/schedule/imports/preview?name=Spring%20promos&timezone=America/Los_Angeles" \
--header "Authorization: Bearer $SCREENRIG_TOKEN" \
--header "Content-Type: text/csv" \
--data-binary @schedule.csv
The answer, abridged:
{
"name": "Spring promos",
"valid": false,
"fingerprint": "FINGERPRINT",
"columns": ["target", "content", "start", "end", "days", "daily_start", "daily_end", "priority", "name"],
"summary": {"rows": 2, "rows_with_errors": 1, "levels": 1, "creates": 1, "updates": 0,
"unchanged": 0, "removes": 0, "screens": 1, "playlists": 1},
"rows": [
{"row": 2, "status": "ok", "level": "LEVEL_KEY"},
{"row": 3, "status": "error",
"errors": [{"column": "content", "code": "unknown_file",
"message": "No file or playlist is named \"happy-hour.mp4\"."}]}
],
"levels": [{"key": "LEVEL_KEY", "action": "create", "home": "screen", "screen_id": "scr_LOBBY",
"content": {"kind": "file", "id": "med_WELCOME", "name": "welcome-spring.jpg"},
"screens": 1, "priority": 0, "from": "2026-11-05T18:00", "until": "2026-11-08T02:00", "rows": [2]}],
"removals": []
}
rows[].cells returns each row's cells as the sheet gave them, so a client corrects them and sends the rows again. valid is true when no row has an error. calendar has the shape of GET /api/schedule/calendar and shows the screens the import changes as they would be after a commit.
Commit sends the same sheet and the fingerprint of its preview, as a member of the JSON body or a query parameter with a file. Send an Idempotency-Key to retry safely.
curl -s https://api.screenrig.ai/api/schedule/imports \
--header "Authorization: Bearer $SCREENRIG_TOKEN" \
--header "Idempotency-Key: spring-promos-1" \
--header "Content-Type: application/json" \
--data @commit.json
The answer holds the preview and applied, the counts of what was created, updated, left unchanged and removed, with the screens written and the playlists made. A commit is refused, and writes nothing, as:
| Problem | Status | Meaning and fix |
|---|---|---|
schedule_import_invalid | 422 | Rows have errors. Correct them and preview again |
schedule_import_stale | 409 | Screens, media, playlists or levels changed since the preview, or the rows read differently. Preview again and commit with the new fingerprint |
GET /api/schedule/imports/template?format=csv and format=xlsx answer the template.
Limits
- 5,000 rows and 8 MiB per sheet.
- 32 own levels per screen and 100 shared levels per project.
- 400 screens' own schedules written per commit. A folder or tag row reaches any number of screens.
- An import name is 1 to 80 characters.