Most automated TV scheduling software hands you a grid and a drag handle. That works until you are running four channels and someone has to rebuild all of them every week.
AI channel scheduling is the alternative: treat the schedule as something you generate. A job reads your library, decides an order, and commits it. When the output is wrong you fix the function, not the grid.
This is how to write that job against the FastPix API. What to query from the video metadata API, where an LLM helps and where it does not, and how to commit the result through the playlist API and onto a channel. Every endpoint here is live.
TL;DR
AI channel scheduling splits into two halves that need different tools. Your scheduler reads five fields per video: duration, genre, series and episode, rights dates, and when it last aired. Query those from the video metadata API with GET /on-demand, then rank the candidates with an LLM if you want thematic grouping. Pack the grid with plain code, because durations have to add up exactly and a model will not. Commit the result through the playlist API as a manual playlist, setting order with PATCH /on-demand/playlists/{playlistId}/media. Do not use smart playlists here: FastPix evaluates their filters once at creation and never again.
What metadata a channel playout scheduler needs
Start here, because this stage gets oversold. A scheduler picks on five things, and none of them come from AI.
| Field | Where it comes from | What it decides |
|---|---|---|
| Duration | FastPix media object | Whether it fits the slot |
| Genre, series, episode | Your CMS | Whether it belongs in the block |
| Rights window | Your contracts system | Whether it is legal to air |
| Last aired | Your own schedule history | Whether it is too soon to repeat |
| Past performance | Cloud Playout channel analytics | Whether it earned the slot |
Only the first and last come from FastPix. The middle three are yours, which is the real reason scheduling stays in your code: a product that does not know your rights model cannot make this decision for you.
GET https://api.fastpix.com/v1/on-demand returns your library with durations and status. Join it against your own catalogue on mediaId and you have the input.
import requests
from requests.auth import HTTPBasicAuth
BASE = "https://api.fastpix.com/v1"
AUTH = HTTPBasicAuth(ACCESS_TOKEN_ID, SECRET_KEY)
def library():
r = requests.get(f"{BASE}/on-demand", auth=AUTH, params={"limit": 100})
r.raise_for_status()
return r.json()["data"]That call works on a free account, so it is worth running against your real library before reading further. Access Token ID and Secret Key come with $25 in credits and no card.
How to add AI metadata to your videos
There is one case where AI-generated metadata changes what is possible: an archive nobody ever described. If ten thousand hours have no summaries and no keywords, a programmer cannot find anything in there to schedule.
Four calls fix that. Each is a PATCH against a mediaId, and each fires a webhook when it finishes:
for job in ("summary", "chapters", "named-entities", "moderation"):
requests.patch(f"{BASE}/on-demand/{media_id}/{job}",
auth=AUTH, json={job.replace("-", ""): True})Two things to keep straight. These read what is said, so you get a summary and the organisations, locations and products mentioned, not visual labels. And they run on uploaded files only, so a live source gets none of it.
Only one of the four matters for scheduling: moderation, because it gives you a flag your code can check before a video is allowed on air. The other three are for search.
How AI channel scheduling works: AI ranks, code fills the grid
Choosing what belongs in an evening block is a judgment about content. Placing it in the grid is arithmetic. Give those to different tools.
An LLM does the first half well. Hand it your candidate list with durations and summaries, and a brief, and let it group and rank.
prompt = f"""Pick 8 candidates for a 3-hour evening comedy block.
Return mediaIds only, ranked, as JSON.
Candidates:
{json.dumps(candidates, indent=2)}"""Do not let it lay out the day. A model that is approximately right about 1440 minutes leaves you with dead air in the overnight block. Packing is a solver problem, and a greedy packer is enough for a single channel:
def pack(slot_minutes, ranked, cleared):
remaining, out = slot_minutes * 60, []
for m in ranked:
if m["id"] not in cleared:
continue
if m["duration_s"] <= remaining:
out.append(m)
remaining -= m["duration_s"]
return out, remaining # remaining seconds get fillercleared is your rights check, applied after ranking and before placing. Keep it a hard filter in code rather than an instruction in the prompt. A model told to respect rights will mostly do it, and mostly is not good enough here.
When to buy a scheduling product instead
Everything above assumes you are building. There is a case where you should not.
If you have run channels for years, you already hold the data a scheduler wants: what aired, in which slot, and how it performed. At that point the schedule is a modelling problem, and dedicated products do it out of the box. Amagi Smart Scheduler, for instance, trains on historical viewership, content affinity and engagement signals to propose a lineup, and it keeps rights rules inside the scheduling engine, so a series licensed for one territory is dropped from another feed without anyone checking.
Rebuilding that is not a weekend. The modelling is the easy half; the rights engine and the traffic rules underneath it are decades of broadcast logic.
| Buy a scheduling product | Build the loop yourself |
|---|---|
| Years of aired history to learn from | A new channel with no history |
| Dozens of channels | A handful |
| Contractual play counts, territory splits | You own it, or licensed it flat |
| A programming team who are not engineers | Engineers, and no traffic desk |
| The schedule is an operational document | The schedule belongs in version control |
Both work. They suit different teams, and the rest of this article is written for the right-hand column.
How to create a playlist with the API
Create the playlist, then add the videos in order. The order of mediaIds in the request body is the playback order, so a correctly sorted list is all you need.
def commit(name, ref, media_ids):
r = requests.post(f"{BASE}/on-demand/playlists", auth=AUTH, json={
"name": name,
"referenceId": ref, # must be unique in the workspace
"type": "manual",
"description": "Generated schedule",
})
pid = r.json()["data"]["id"]
requests.patch(f"{BASE}/on-demand/playlists/{pid}/media",
auth=AUTH, json={"mediaIds": media_ids})
return pidreferenceId is required and must be unique per workspace, which makes it the natural place to put your own schedule key, something like channel-01-2026-W34. That gives you idempotency and a way to find the playlist again without storing the UUID.
To change the order later, PUT to the same path. Two rules catch people out: you must include every video already in the playlist, and you cannot add new ones this way. Partial reorder is rejected with a validation error.
requests.put(f"{BASE}/on-demand/playlists/{pid}/media",
auth=AUTH, json={"mediaIds": full_reordered_list})The ceiling is 1,000 videos per playlist, for both types.
Building a channel this way is the point at which a card-free account with $25 in credits is worth spending, because the gaps you find will be in your own catalogue rather than in the API.
Smart playlists vs manual playlists
FastPix has a second playlist type that looks perfect for this and is not.
Smart playlists auto-populate from filters, which sounds like exactly what a scheduler wants. Two things rule them out:
- They filter on dates only.
createdDate,updatedDate, or both. Not genre, not tags, not duration. The API reference is explicit about this. - They are evaluated once, at creation. Videos uploaded afterwards that match the filter are never added. To refresh the selection you create a new playlist.
So a smart playlist is a snapshot of "everything uploaded between these dates," frozen at the moment you made it. Useful for a "new this month" rail. Useless for a channel that has to reflect a library that keeps changing.
Build manual playlists from your own query. You are doing the filtering anyway.
How to send a playlist to a cloud playout channel
The FastPix Cloud Playout channel has three levels: the channel, the programs inside it, and the items inside each program. A playlist goes in at the program level, in one call.
def commit_to_channel(channel_id, program_id, playlist_id):
r = requests.post(
f"{BASE}/cloud-playout/channels/{channel_id}/programs/{program_id}/playlist",
auth=AUTH, json={"playlistId": playlist_id})
r.raise_for_status()
return r.json()["data"]Items are appended in playlist order, which is why the ordering work upstream matters. Two things to check first: the playlist needs at least one item, and every media item needs a public access policy.
The detail that shapes the job: the playlist is copied, not linked. Items are taken as they are at the moment of the request, so editing the playlist afterwards does not change the program. A new lineup means a new playlist.
That call plus the two below are the whole linear channel scheduling API surface you need for a weekly job. Creating the channel itself is POST /cloud-playout/channels, with a name, a type of loop or schedule, and startTime and endTime in yyyy-MM-dd'T'HH:mm:ss. A loop channel repeats its programs; a schedule channel plays items at set times. Channels start in draft, so fill the programs first, then POST /cloud-playout/channels/{channelId}/activate to put it on air.
How to run the scheduler every week
The whole thing is a scheduled job. Read the library, rank, filter, pack, commit. Version the output so you can diff last week against this week and see what your own logic did.
Two habits worth building in from the start:
- Log the rejects. Every video that failed the rights filter or lost a slot, with the reason. This is the only way to debug a lineup that looks wrong.
- Keep the previous playlist. Rolling back is creating a playlist you already have the media list for. Deleting a playlist does not touch the videos.
Where the performance scores that feed this job come from, and the guardrails that stop them chasing the wrong number, is covered in linear channel analytics.
Where to start
Write the query first, before any of the AI parts. Pull GET /on-demand, join it to your catalogue, and print the five fields for a week's worth of candidates. That one step tells you whether you have a scheduling problem or a metadata problem, and it is usually the second.
Add the packer next, then the LLM ranking, then the commit. The ranking is the part that feels like the product and the least likely to be what is broken.
Every call in this article runs on a free account, so you can have the query and the commit working before you decide anything about a channel. Grab an API key and $25 in credits, no card. When you are ready to put a channel behind it, book a demo and bring a real catalogue.
Frequently Asked Questions (FAQs)
What is AI channel scheduling?
AI channel scheduling is generating a linear TV lineup from code and data instead of building it by hand in a grid. A job reads your video library and past performance, ranks the candidates, fits them to the clock, and commits the result as an ordered playlist. The model handles curation; the placement stays deterministic.
How do you automate a TV channel schedule?
Run it as a scheduled job with four steps. Query your library for duration, genre, rights dates, and last-aired date. Rank the candidates, filter out anything outside its rights window, then pack the results until the day is full. Commit the final order as a playlist the channel plays.
Can an LLM build a TV schedule?
An LLM can shortlist and group candidates well, but it should not lay out the grid. Filling exactly 1,440 minutes a day, honoring rights dates, and maintaining repeat spacing are constraint problems where an approximate answer can produce dead air. Use the model to rank and use code or a solver to place the content.
What metadata do you need to schedule a TV channel?
Five fields per video: duration, genre with series and episode, the rights window, when it last aired, and how it performed. Duration and performance come from your video platform. Genre, rights, and air history come from your own systems, which is why scheduling logic stays in your code.
What is the difference between a manual and a smart playlist?
In a manual playlist, you add each video explicitly and control the order. A smart playlist auto-populates from metadata filters at the moment you create it. Both use the same create endpoint, with a type field of manual or smart determining the behavior, and both hold up to 1,000 videos.
Do smart playlists re-evaluate automatically?
No. FastPix evaluates smart playlist filters only at creation time. Videos uploaded later that match the filter are never added. To refresh the selection, you create a new playlist, which is why a scheduler running on a cycle should build manual playlists instead.
How do I reorder videos in a playlist?
Send a PUT request to /v1/on-demand/playlists/{playlistId}/media with every existing mediaId in the new order. Partial reordering is rejected with a validation error, and this endpoint cannot add new videos. Use the add-media endpoint for that.
How many videos can a playlist hold?
A maximum of 1,000 videos, for both manual and smart playlists.
Do you need scheduling software to run a linear channel?
Not if you are running a handful of channels with a simple rights model and have engineers. The selection logic is a scheduled job of modest size. Dedicated scheduling products earn their price when you have years of aired history, dozens of channels, contractual play counts and territory splits, or a programming team who are not engineers.





