Scripted schedules give you full programmatic control over playout construction by calling ErsatzTV’s Scripted Schedule REST API from a Python script. Where YAML schedules express a fixed declarative loop, a scripted schedule can inspect the current playout context, make branching decisions, query external data, and call any API endpoint in any order. This makes scripted schedules ideal for complex or dynamic channel programming — nightly builds that vary by the day of the week, channels that react to new media additions, or deeply customized marathon layouts that require logic beyond what YAML can express. The API is auto-generated from the OpenAPI specification atDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/ErsatzTV/legacy/llms.txt
Use this file to discover all available pages before exploring further.
ErsatzTV/wwwroot/openapi/scripted-schedule.json. A Python client library (etv_client) is generated from this spec and is imported by your scripts.
How Scripted Schedules Work
When ErsatzTV triggers a scripted playout build, it callsentrypoint.py with four arguments: the ErsatzTV host URL, a build ID (UUID), the build mode (reset or continue), and the name of your script module. The entrypoint imports your script, creates an API client, fetches the current PlayoutContext, and then calls three functions from your script in sequence:
define_content— declare all content sources the playout will use.reset_playout— (called only inresetmode) set the playout’s starting time and state.build_playout— add content, padding, and control instructions to the playout.
Build Modes
| Mode | Behavior |
|---|---|
reset | Clears the existing playout and rebuilds it from scratch. Both reset_playout and build_playout are called. |
continue | Extends an existing playout from its current end time. Only build_playout is called. |
The Entrypoint
The entrypoint script (scripts/scripted-schedules/entrypoint.py) is the runtime harness for all scripted schedules. You do not modify this file — it handles argument parsing, module loading, API client setup, and orchestration:
scripts/<script_name>.py relative to the entrypoint directory and must export exactly three functions: define_content, reset_playout, and build_playout.
Required Script Functions
Every scripted schedule module must implement the following three functions with the exact signatures shown:define_content(api, context, build_id)
Called first on every build. Use this function to register all content sources the playout will reference. Each add_* call below adds a named content source to the build session.
reset_playout(api, context, build_id)
Called only in reset mode. Use this function to position the playout’s start time (via wait_until or wait_until_exact) and perform any one-time initialization. Return a new PlayoutContext if you want build_playout to see the updated time, or return None to let the entrypoint re-fetch it automatically.
build_playout(api, context, build_id)
Called on every build (both reset and continue). This is where you add the actual programming — content items, padding, EPG groups, watermarks, and overlays. Called in a loop by the playout engine until context.is_done is True.
The PlayoutContext
Every API call that adds or modifies playout content returns (or the entrypoint fetches) aPlayoutContext with four fields:
| Field | Type | Description |
|---|---|---|
current_time | datetime | The current position of the playout build cursor. |
start_time | datetime | The wall-clock time the playout build started from. |
finish_time | datetime | The target end time for this build cycle. |
is_done | bool | True when current_time has reached finish_time. |
context.is_done in a loop to continue building until the target time is filled.
Content Registration API
Call these methods insidedefine_content to register named content sources:
| Method | Body Schema | Description |
|---|---|---|
add_collection(build_id, body) | ContentCollection | Add a named collection by its ErsatzTV collection name. |
add_multi_collection(build_id, body) | ContentMultiCollection | Add a named multi-collection. |
add_smart_collection(build_id, body) | ContentSmartCollection | Add a named smart collection. |
add_show(build_id, body) | ContentShow | Add a TV show by title or external GUID. |
add_search(build_id, body) | ContentSearch | Add content matching a search query string. |
add_marathon(build_id, body) | ContentMarathon | Add a marathon content source with grouping and ordering options. |
add_playlist(build_id, body) | ContentPlaylist | Add an existing playlist from a playlist group. |
create_playlist(build_id, body) | ContentCreatePlaylist | Dynamically create a playlist for this build session. |
Playout Building API
Call these methods insidebuild_playout to add content and control playback:
Add Content
| Method | Key Fields | Description |
|---|---|---|
add_all(build_id, body) | content | Add every item in the referenced content source. Returns an updated PlayoutContext. |
add_count(build_id, body) | content, count | Add exactly count items. Supports filler_kind, custom_title, disable_watermarks. |
add_duration(build_id, body) | content, duration | Add content until the specified duration is filled (e.g., "30 minutes"). Supports trim, offline_tail, discard_attempts, stop_before_end, fallback. |
Padding
| Method | Key Fields | Description |
|---|---|---|
pad_to_next(build_id, body) | content, minutes | Add content until the next multiple of minutes from the current time (e.g., 30 pads to the next half-hour boundary). Supports fallback, trim, discard_attempts, offline_tail. |
pad_until(build_id, body) | content, when | Add content until a specific time of day (e.g., "20:00"). Set tomorrow: true to target that time the next day. |
pad_until_exact(build_id, body) | content, when | Add content until an exact datetime timestamp. |
Timing Control
| Method | Key Fields | Description |
|---|---|---|
wait_until(build_id, body) | when | Insert unscheduled (offline) time until a time of day. Set tomorrow: true to target the next day. Set rewind_on_reset: true to allow the cursor to move backward on reset. |
wait_until_exact(build_id, body) | when | Insert offline time until an exact datetime timestamp. |
Skip and Peek
| Method | Key Fields | Description |
|---|---|---|
skip_items(build_id, body) | content, count | Advance the content enumerator by count items without adding them to the playout. |
skip_to_item(build_id, body) | content, season, episode | Advance a show’s enumerator to a specific season and episode number. |
peek_next(build_id, content) | content (path param) | Returns a PeekItemDuration object with a milliseconds field representing the duration of the next item in the content source, without advancing the enumerator. Useful for look-ahead logic. |
EPG Grouping
| Method | Key Fields | Description |
|---|---|---|
start_epg_group(build_id, body) | advance, custom_title | Open a new EPG guide group. Set advance: false to continue the current group. Set custom_title to override all item titles within the group. |
stop_epg_group(build_id) | — | Close the current EPG group. |
Overlay Controls
| Method | Key Fields | Description |
|---|---|---|
graphics_on(build_id, body) | graphics (list of names), variables | Turn on one or more named graphics elements (lower-thirds, bugs). Pass a variables dict for template substitution. |
graphics_off(build_id, body) | graphics (list of names) | Turn off named graphics elements. Pass an empty list to turn off all active elements. |
watermark_on(build_id, body) | watermark (list of names) | Turn on one or more named channel watermarks. |
watermark_off(build_id, body) | watermark (list of names) | Turn off named watermarks. Pass an empty list to turn off all active scripted watermarks. |
pre_roll_on(build_id, body) | playlist | Activate a pre-roll playlist that plays before each subsequent content item. |
pre_roll_off(build_id) | — | Deactivate the active pre-roll playlist. |
A Complete Minimal Example
Script Creation and Execution Flow
Install dependencies
Ensure
etv_client is installed in the Python environment used by ErsatzTV. The client is generated from ErsatzTV/wwwroot/openapi/scripted-schedule.json using an OpenAPI Python generator.Write your script module
Create a file at
scripts/<your_script_name>.py in the scripted-schedules directory. Implement the three required functions: define_content, reset_playout, and build_playout. Each receives (api: ScriptedScheduleApi, context: PlayoutContext, build_id: UUID) as arguments.Configure the playout in ErsatzTV
In the ErsatzTV UI, navigate to your channel’s Playout settings and set the Schedule Kind to Scripted. Point the Script field to your script module name (without the
.py extension). Save the playout.Trigger a reset build
Force a full rebuild by triggering a Reset on the playout. ErsatzTV calls
entrypoint.py with mode reset, which runs all three of your functions. Inspect the playout timeline in the UI to verify the output looks correct.Tips and Best Practices
Always check is_done
Wrap your
build_playout loop body in while not context.is_done: and refresh context from each API response. This prevents the script from overrunning the build window or running infinitely.Use peek_next for look-ahead
Call
peek_next(build_id, content_key) to inspect the duration of the next item before committing to add_count or add_duration. This enables smarter scheduling decisions, such as skipping a film that is too long to finish before a hard time boundary.Register all content in define_content
All
add_collection, add_show, and similar calls must happen in define_content, not in build_playout. This ensures content is available in both reset and continue mode builds without duplication.Use EPG groups for multi-segment programs
Wrap related content items in
start_epg_group / stop_epg_group calls to merge them into a single EPG entry. This is especially useful when a movie is followed by its trailer or a news block is followed by a station ID.The full Scripted Schedule REST API reference — including all request and response schemas — is available at /api/scripted-schedule.
