-
Notifications
You must be signed in to change notification settings - Fork 0
API Reference
Welcome to the EXStreamTV API documentation. This guide explains how to use the API to control your streaming server, manage channels, organize content, and build custom schedules. For HDHomeRun emulation, streaming lifecycle, and observability, see the Platform Guide.
- Getting Started
- How the API Works
- Core APIs
- Scheduling APIs
- Content Enhancement APIs
- Advanced APIs
- Streaming & IPTV
- System & Settings
- Error Handling
- Interactive Documentation
An API (Application Programming Interface) is a way for software programs to talk to each other. Think of it like a waiter at a restaurant: you tell the waiter what you want (make a request), and the waiter brings you your food (returns a response). The API is the waiter between you and the EXStreamTV server.
All API requests go to this address:
http://localhost:8411/api
If you're accessing EXStreamTV from another computer on your network, replace localhost with your server's IP address (like 192.168.1.100).
Here's how to get a list of all your channels:
Using a web browser: Just visit http://localhost:8411/api/channels
Using the command line:
curl http://localhost:8411/api/channelsUsing JavaScript:
const response = await fetch('http://localhost:8411/api/channels');
const data = await response.json();
console.log(data);By default, EXStreamTV doesn't require a password for local use. For remote access, you can enable API key authentication in your configuration.
The API uses different "methods" to perform different actions:
| Method | What It Does | Example |
|---|---|---|
| GET | Retrieve information | Get a list of channels |
| POST | Create something new | Create a new channel |
| PUT | Update something existing | Change a channel's name |
| DELETE | Remove something | Delete a channel |
All data is sent and received as JSON (JavaScript Object Notation). JSON is a simple text format that looks like this:
{
"name": "Movie Channel",
"number": 5,
"enabled": true
}When you make a request, you'll get back:
- A status code - A number indicating success or failure
- Response data - The information you requested (in JSON format)
Common status codes:
- 200 - Success! Everything worked.
- 201 - Created! A new item was made.
- 204 - Deleted! The item was removed.
- 400 - Bad request. Something was wrong with your request.
- 404 - Not found. The item doesn't exist.
- 500 - Server error. Something went wrong on our end.
These are the fundamental APIs for managing your streaming content.
Channels are virtual TV stations that stream your content. Each channel has a number, name, and plays content from a playlist or schedule.
Get a list of every channel you've created.
GET /api/channels
Example Response:
[
{
"id": 1,
"number": 1,
"name": "Movies 24/7",
"group": "Entertainment",
"enabled": true,
"logo_url": "/api/channels/1/logo"
},
{
"id": 2,
"number": 2,
"name": "Classic TV",
"group": "Entertainment",
"enabled": true
}
]Get detailed information about one specific channel.
GET /api/channels/{id}
Replace {id} with the channel's ID number.
Example: GET /api/channels/1
Add a new channel to your lineup.
POST /api/channels
What to send:
{
"number": 3,
"name": "Kids Shows",
"group": "Family",
"enabled": true
}Change a channel's settings.
PUT /api/channels/{id}
What to send: Only include the fields you want to change.
{
"name": "Children's Programming"
}Remove a channel permanently.
DELETE /api/channels/{id}
See what filler content is assigned to a channel.
GET /api/channels/{id}/filler
Example Response:
{
"channel_id": 1,
"fallback_filler_id": 5,
"pre_roll_filler_id": 2,
"post_roll_filler_id": null,
"fallback_filler": {
"id": 5,
"name": "Commercial Breaks",
"filler_mode": "duration"
}
}Assign filler presets to a channel.
PUT /api/channels/{id}/filler
What to send:
{
"fallback_filler_id": 5,
"pre_roll_filler_id": 2
}See what decorative content (bumpers, station IDs) is assigned.
GET /api/channels/{id}/deco
Assign deco groups to a channel.
PUT /api/channels/{id}/deco
What to send:
{
"deco_group_id": 3,
"bumper_group_id": 1
}See what's scheduled to play on a channel.
GET /api/channels/{id}/programming?hours=24
Parameters:
-
hours- How many hours of programming to show (default: 24)
Playlists are ordered lists of media items that play in sequence.
GET /api/playlists
GET /api/playlists/{id}
Returns the playlist with all its items.
POST /api/playlists
What to send:
{
"name": "Saturday Night Movies",
"description": "Action films for the weekend"
}POST /api/playlists/{id}/items/{media_id}
DELETE /api/playlists/{id}/items/{media_id}
POST /api/playlists/{id}/reorder
What to send:
{
"item_ids": [5, 3, 1, 4, 2]
}The items will be reordered to match the order you specify.
Collections are groups of related media items. Unlike playlists, collections don't have a specific order—they're just ways to organize your content.
GET /api/collections
GET /api/collections/{id}
POST /api/collections
What to send:
{
"name": "80s Action Movies",
"description": "The best action films from the 1980s"
}Smart collections automatically find and include media based on search criteria.
POST /api/collections/smart
Parameters:
-
name- Name for the collection -
search_query- What to search for -
description- Optional description
Example: Create a collection that automatically includes all Star Wars content:
POST /api/collections/smart?name=Star Wars&search_query=star wars
Re-run the search to update the collection with new matching items.
POST /api/collections/smart/{id}/refresh
POST /api/collections/{id}/items/{media_id}
DELETE /api/collections/{id}/items/{media_id}
Media items are individual videos, movies, or TV episodes in your library.
GET /api/media
Optional filters:
-
library_id- Only show items from a specific library -
media_type- Filter by type:movie,episode,other_video -
query- Search by title -
limit- Maximum number of results -
offset- Skip this many results (for pagination)
GET /api/media/{id}
Returns complete information about a media item including file paths, duration, and metadata.
These APIs control when and how content plays on your channels.
Persist JSON snapshots of channel schedule state before risky apply operations; revert later if needed. Requires DB migration 006 (schedule_history table).
POST /api/schedule-history/capture
Body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
channel_ids |
array of integers | yes | Channels to include in the snapshot |
persona_id |
string | no | Optional tag for automation/persona flows |
label |
string | no | Human-readable label |
Response (201): { "id": <history_id>, "persona_id": ..., "label": ... }
POST /api/schedule-history/{history_id}/revert?persona_id=<optional>
Restores channels from the stored pre-apply snapshot. Returns { "status": "ok", "items_restored": <n> }.
Errors: 404 if the row is missing or persona_id does not match; 409 if the entry was not marked applied, has no snapshot, or revert rules reject the operation.
Schedules define what content plays and in what order.
GET /api/schedules
GET /api/schedules/{id}
POST /api/schedules
What to send:
{
"name": "Weekday Programming",
"keep_multi_part_episodes": true,
"shuffle_schedule_items": false
}PUT /api/schedules/{id}
DELETE /api/schedules/{id}
A playout is the actual running program for a channel—it turns your schedule into a real-time stream of content.
GET /api/playouts
GET /api/playouts/{id}
See what's currently playing on a playout.
GET /api/playouts/{id}/current
See what's coming up next.
GET /api/playouts/{id}/upcoming?count=10
Jump to the next item in the playout.
POST /api/playouts/{id}/skip
Blocks are time-based programming segments. For example, you might have a "Morning Cartoons" block from 6 AM to 9 AM.
Block groups help you organize related blocks together.
GET /api/block-groups
Example Response:
[
{
"id": 1,
"name": "Weekday Blocks",
"block_count": 5
},
{
"id": 2,
"name": "Weekend Blocks",
"block_count": 3
}
]POST /api/block-groups
What to send:
{
"name": "Holiday Specials"
}GET /api/block-groups/{id}
PUT /api/block-groups/{id}
Deletes the group and all blocks inside it.
DELETE /api/block-groups/{id}
GET /api/blocks
Optional filter:
-
group_id- Only show blocks from a specific group
Example Response:
[
{
"id": 1,
"name": "Morning Cartoons",
"group_id": 1,
"start_time": "06:00",
"duration_minutes": 180,
"days_of_week": 127,
"items": []
}
]POST /api/blocks
What to send:
{
"name": "Prime Time Movies",
"group_id": 1,
"start_time": "20:00",
"duration_minutes": 180,
"days_of_week": 127
}Understanding days_of_week:
This is a number that represents which days the block is active. Each day has a value:
| Day | Value |
|---|---|
| Sunday | 1 |
| Monday | 2 |
| Tuesday | 4 |
| Wednesday | 8 |
| Thursday | 16 |
| Friday | 32 |
| Saturday | 64 |
Add up the values for the days you want. For example:
- All days = 1+2+4+8+16+32+64 = 127
- Weekdays only = 2+4+8+16+32 = 62
- Weekends only = 1+64 = 65
GET /api/blocks/{id}
PUT /api/blocks/{id}
DELETE /api/blocks/{id}
POST /api/blocks/{id}/items
What to send:
{
"collection_type": "collection",
"collection_id": 5,
"playback_order": "shuffled",
"include_in_guide": true
}Playback order options:
-
chronological- Play in order -
shuffled- Randomize the order -
random- Pick randomly each time
PUT /api/blocks/{id}/items/{item_id}
DELETE /api/blocks/{id}/items/{item_id}
POST /api/blocks/{id}/items/reorder
What to send:
{
"item_ids": [3, 1, 2]
}Templates are reusable schedule patterns. Create a template once, then apply it to any channel.
GET /api/template-groups
POST /api/template-groups
What to send:
{
"name": "Standard Schedules"
}GET /api/template-groups/{id}
PUT /api/template-groups/{id}
DELETE /api/template-groups/{id}
GET /api/templates
Optional filter:
-
group_id- Only show templates from a specific group
POST /api/templates
What to send:
{
"name": "Weekday Schedule",
"group_id": 1,
"is_enabled": true
}GET /api/templates/{id}
PUT /api/templates/{id}
DELETE /api/templates/{id}
POST /api/templates/{id}/items
What to send:
{
"start_time": "18:00",
"block_id": 5,
"playback_order": "chronological"
}Or reference a collection directly:
{
"start_time": "18:00",
"collection_type": "collection",
"collection_id": 10,
"playback_order": "shuffled"
}PUT /api/templates/{id}/items/{item_id}
DELETE /api/templates/{id}/items/{item_id}
POST /api/templates/{id}/apply/{channel_id}
What to send:
{
"day_of_week": null
}Set day_of_week to a number (0-6, where 0=Sunday) to apply only on that day, or null for all days.
These APIs help you add polish to your channels with filler content and branding.
Filler presets define what content plays in gaps between programs. This includes commercials, bumpers, or any short content.
GET /api/filler-presets
Example Response:
[
{
"id": 1,
"name": "Commercial Breaks",
"filler_mode": "duration",
"duration_seconds": 180,
"playback_order": "shuffled",
"allow_repeats": true,
"items": []
}
]POST /api/filler-presets
What to send:
{
"name": "30-Second Bumpers",
"filler_mode": "duration",
"duration_seconds": 30,
"playback_order": "shuffled",
"allow_repeats": false
}Filler modes:
| Mode | Description | Required Field |
|---|---|---|
duration |
Fill a specific amount of time | duration_seconds |
count |
Play a specific number of items | count |
pad |
Fill until the next time boundary | pad_to_minutes |
Examples:
- Fill 3 minutes:
"filler_mode": "duration", "duration_seconds": 180 - Play 2 items:
"filler_mode": "count", "count": 2 - Pad to quarter hour:
"filler_mode": "pad", "pad_to_minutes": 15
GET /api/filler-presets/{id}
PUT /api/filler-presets/{id}
DELETE /api/filler-presets/{id}
POST /api/filler-presets/{id}/items
What to send (for a collection):
{
"collection_type": "collection",
"collection_id": 15,
"weight": 1
}What to send (for a single media item):
{
"media_item_id": 42,
"weight": 2
}Understanding weight: Higher weight means the item is more likely to be selected. An item with weight 2 is twice as likely to be chosen as an item with weight 1.
PUT /api/filler-presets/{id}/items/{item_id}
DELETE /api/filler-presets/{id}/items/{item_id}
Deco items are decorative content like bumpers, station IDs, promos, and credits that add professional polish to your channels.
| Type | Description |
|---|---|
bumper |
Short transitional clips between programs |
commercial |
Advertisement or promotional content |
station_id |
Station identification clips ("You're watching...") |
promo |
Program promotional content |
credits |
Credit sequences |
GET /api/deco-groups
POST /api/deco-groups
What to send:
{
"name": "Channel 5 Branding"
}GET /api/deco-groups/{id}
Returns the group with all its deco items.
PUT /api/deco-groups/{id}
DELETE /api/deco-groups/{id}
GET /api/deco
Optional filters:
-
group_id- Only show items from a specific group -
deco_type- Filter by type (bumper, commercial, etc.)
POST /api/deco
What to send:
{
"name": "Station ID - Evening",
"group_id": 1,
"deco_type": "station_id",
"file_path": "/media/branding/station_id_evening.mp4",
"duration_seconds": 10,
"weight": 1
}GET /api/deco/{id}
PUT /api/deco/{id}
DELETE /api/deco/{id}
GET /api/deco/types
Returns a list of all valid deco types with descriptions.
Multi-collections combine multiple collections into one, making it easy to schedule diverse content together.
GET /api/collections/multi
POST /api/collections/multi
Parameters:
-
name- Name for the multi-collection -
description- Optional description -
collection_ids- Optional list of collection IDs to include initially
Example:
POST /api/collections/multi?name=All Movies&description=Every movie collection combined
With initial collections:
{
"name": "All Movies",
"collection_ids": [1, 5, 8, 12]
}GET /api/collections/multi/{id}
Example Response:
{
"id": 1,
"name": "All Movies",
"description": "Every movie collection combined",
"collections": [
{"id": 1, "name": "Action Movies", "position": 0},
{"id": 5, "name": "Comedy Movies", "position": 1},
{"id": 8, "name": "Drama Movies", "position": 2}
]
}PUT /api/collections/multi/{id}
DELETE /api/collections/multi/{id}
POST /api/collections/multi/{multi_id}/collections/{collection_id}
DELETE /api/collections/multi/{multi_id}/collections/{collection_id}
These APIs provide powerful programmatic control over your playouts.
The Scripted Schedule API lets you build playout schedules step-by-step through code. This is perfect for creating complex, dynamic schedules programmatically.
- Start a build session for a playout
- Add content using various commands
- Control timing with padding and wait commands
- Toggle features like watermarks and graphics
- Commit to save or cancel to discard
POST /api/playouts/{playout_id}/build/start
Example Response:
{
"session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"playout_id": 1,
"status": "building",
"current_time": "2026-01-17T10:00:00Z",
"expires_at": "2026-01-17T11:00:00Z",
"message": "Build session started"
}Important: Save the session_id—you'll need it for all subsequent commands.
See the current state of your build session.
GET /api/scripted/build/{session_id}/context
Example Response:
{
"build_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"playout_id": 1,
"status": "building",
"current_time": "2026-01-17T12:30:00Z",
"items_buffered": 15,
"watermark_enabled": true,
"graphics_enabled": true,
"pre_roll_enabled": true,
"epg_group_active": false,
"expires_at": "2026-01-17T13:00:00Z"
}Save all your changes to the playout.
POST /api/playouts/{playout_id}/build/commit
Optional parameter:
-
session_id- If you have multiple sessions
Discard all changes without saving.
POST /api/playouts/{playout_id}/build/cancel
See all build sessions for a playout.
GET /api/playouts/{playout_id}/build/sessions
Optional filter:
-
status_filter- Filter by status:building,committed,cancelled
Add content from a collection to your schedule.
POST /api/scripted/build/{session_id}/add-collection
What to send:
{
"collection_id": 5,
"count": 3,
"playback_order": "shuffled"
}Options:
-
collection_id- Which collection to add -
count- How many items to add (optional) -
duration_minutes- Add items until this duration is filled (optional) -
playback_order-chronological,shuffled, orrandom
Add all episodes of a show in order.
POST /api/scripted/build/{session_id}/add-marathon
What to send:
{
"show_id": 42,
"playback_order": "chronological"
}Add content from a multi-collection.
POST /api/scripted/build/{session_id}/add-multi-collection
What to send:
{
"multi_collection_id": 3,
"playback_order": "shuffled"
}POST /api/scripted/build/{session_id}/add-playlist
What to send:
{
"playlist_id": 10,
"count": 5
}Create a new playlist on-the-fly and add it to the schedule.
POST /api/scripted/build/{session_id}/create-playlist
What to send:
{
"name": "Tonight's Special",
"media_item_ids": [101, 102, 103, 104]
}Add media items matching a search query.
POST /api/scripted/build/{session_id}/add-search
What to send:
{
"query": "christmas",
"count": 10,
"media_type": "movie"
}POST /api/scripted/build/{session_id}/add-smart-collection?collection_id=15&count=5
POST /api/scripted/build/{session_id}/add-show
What to send:
{
"show_title": "Friends",
"season": 3,
"count": 4
}POST /api/scripted/build/{session_id}/add-all?collection_id=5
POST /api/scripted/build/{session_id}/add-count
What to send:
{
"collection_id": 5,
"count": 10
}Fill a specific amount of time with content.
POST /api/scripted/build/{session_id}/add-duration
What to send:
{
"collection_id": 5,
"duration_minutes": 120
}Fill time until the next quarter-hour, half-hour, or hour.
POST /api/scripted/build/{session_id}/pad-to-next
What to send:
{
"minutes": 30,
"filler_preset_id": 5
}This fills with filler content until the next 30-minute mark (e.g., 10:30, 11:00, 11:30).
Fill time until a specific clock time.
POST /api/scripted/build/{session_id}/pad-until
What to send:
{
"target_time": "20:00",
"filler_preset_id": 5
}Same as above but with exact precision.
POST /api/scripted/build/{session_id}/pad-until-exact
Leave dead air until a specific time.
POST /api/scripted/build/{session_id}/wait-until
What to send:
{
"target_time": "18:00"
}POST /api/scripted/build/{session_id}/wait-until-exact
See what's next without consuming it.
GET /api/scripted/build/{session_id}/peek-next/{content_type}
Content types: collection, playlist, show
Skip over items in the current collection.
POST /api/scripted/build/{session_id}/skip-items
What to send:
{
"count": 2,
"collection_id": 5
}Jump to a specific item in a collection.
POST /api/scripted/build/{session_id}/skip-to-item
What to send:
{
"item_index": 10,
"collection_id": 5
}Group upcoming items under a single title in the program guide.
POST /api/scripted/build/{session_id}/epg-group/start
What to send:
{
"title": "Movie Marathon"
}All items added after this will appear under "Movie Marathon" in the guide.
End the current EPG grouping.
POST /api/scripted/build/{session_id}/epg-group/stop
Turn on-screen graphics on or off.
POST /api/scripted/build/{session_id}/graphics/on
POST /api/scripted/build/{session_id}/graphics/off
Turn the channel watermark on or off.
POST /api/scripted/build/{session_id}/watermark/on
POST /api/scripted/build/{session_id}/watermark/off
Turn pre-roll content on or off.
POST /api/scripted/build/{session_id}/pre-roll/on
POST /api/scripted/build/{session_id}/pre-roll/off
Get an M3U playlist file for all your channels.
GET /iptv/channels.m3u
Get XMLTV format electronic program guide data.
GET /iptv/xmltv.xml?hours=24
EXStreamTV emulates an HDHomeRun device for Plex, Jellyfin, and Emby. DeviceID must be 8 hex chars. See Platform Guide §3.
GET /hdhomerun/discover.json
GET /discover.json (redirects to /hdhomerun/discover.json)
Returns: FriendlyName, ModelNumber, DeviceID, BaseURL, LineupURL, GuideURL, TunerCount.
GET /hdhomerun/lineup.json
GET /lineup.json (redirects to /hdhomerun/lineup.json)
Returns: Array of { GuideNumber, GuideName, URL } for enabled channels.
GET /hdhomerun/tuner{N}/stream?channel=auto:v{channel_number}
Streams MPEG-TS for the tuned channel. Plex uses the url parameter; EXStreamTV supports both.
GET /metrics
Returns Prometheus text exposition format. See Observability for metric reference.
Check if the server is running properly.
GET /api/health
Example Response:
{
"status": "healthy",
"version": "2.0.0",
"database": "ok",
"ffmpeg": "ok"
}GET /api/dashboard/stats
GET /api/system/info
GET /api/logs
When something goes wrong, the API returns an error response:
{
"detail": "Channel not found",
"status_code": 404
}| Code | Meaning | What to Do |
|---|---|---|
| 400 | Bad Request | Check your request format and required fields |
| 404 | Not Found | The item you're looking for doesn't exist |
| 409 | Conflict | The item already exists (duplicate) |
| 422 | Validation Error | Check your data types and values |
| 500 | Server Error | Try again; check server logs if it persists |
When EXStreamTV is running, you can access interactive API documentation:
-
Swagger UI: http://localhost:8411/api/docs
- Try out API calls directly in your browser
- See all available endpoints
- View request/response schemas
-
ReDoc: http://localhost:8411/api/redoc
- Clean, readable documentation
- Great for reference
| Task | Method | Endpoint |
|---|---|---|
| List channels | GET | /api/channels |
| Create channel | POST | /api/channels |
| Delete channel | DELETE | /api/channels/{id} |
| List playlists | GET | /api/playlists |
| Get M3U playlist | GET | /iptv/channels.m3u |
| Start stream | GET | /api/channels/{id}/stream.m3u8 |
| Health check | GET | /api/health |
| Feature | Endpoints |
|---|---|
| Time Blocks |
/api/blocks, /api/block-groups
|
| Templates |
/api/templates, /api/template-groups
|
| Filler Content | /api/filler-presets |
| Bumpers & Station IDs |
/api/deco, /api/deco-groups
|
| Multi-Collections | /api/collections/multi |
| Scripted Schedules | /api/scripted/build/* |
| Build Sessions | /api/playouts/{id}/build/* |
The AI Self-Healing system provides autonomous issue detection and resolution.
GET /api/ai/health
Example Response:
{
"log_collector": {
"running": true,
"buffer_size": 5000,
"total_events": 125000,
"errors_count": 42
},
"ffmpeg_monitor": {
"channels_monitored": 12,
"total_errors": 15,
"active_predictions": 2
},
"pattern_detector": {
"patterns_detected": 8,
"predictions_made": 25,
"accuracy": 0.88
},
"auto_resolver": {
"enabled": true,
"total_resolutions": 45,
"success_rate": 0.93,
"fixes_this_hour": 3
}
}GET /api/ai/channels/{channel_id}/health
Example Response:
{
"channel_id": 1,
"status": "healthy",
"current_fps": 29.97,
"expected_fps": 30.0,
"current_speed": 1.02,
"current_bitrate_kbps": 4250,
"dropped_frames": 5,
"duplicate_frames": 12,
"error_count": 0,
"restart_count": 1
}GET /api/ai/errors?minutes=60&max_errors=50
GET /api/ai/sessions
Example Response:
{
"total_sessions": 15,
"sessions_by_channel": {
"1": 8,
"2": 4,
"3": 3
},
"sessions": [
{
"session_id": "abc123",
"channel_id": 1,
"client_ip": "192.168.1.100",
"state": "active",
"bytes_sent": 125000000,
"duration_seconds": 3600
}
]
}POST /api/database/backup
What to send:
{
"description": "Manual backup before maintenance",
"compress": true
}GET /api/database/backups
POST /api/database/restore
What to send:
{
"backup_path": "backups/exstreamtv_backup_20260131.db.gz",
"create_safety_backup": true
}- Check the Quick Start Guide
- Read the System Design
- Read the Tunarr/dizqueTV Integration
- Use the interactive docs at
/api/docs - View streaming logs at
/logs
Last Revised: 2026-03-20
Getting Started
Guides
- AI-Setup
- Channel-Creation-Guide
- Local-Media
- Hardware-Transcoding
- macOS-App-Guide
- Navigation-Guide
- Streaming-Stability
- Advanced-Scheduling
Reference
- API-Reference
- System-Design
- Architecture-Diagrams
- Pattern-Refactor-Sources
- ADR-Channel-Manager-Database
- EXStreamTV-UI-Architecture
- Architecture
- Streaming-Internals
- HDHomeRun-Emulation
- Metadata-And-XMLTV
- AI-Agent-And-Containment
- Restart-Safety-Model
- Observability
- Troubleshooting
- Log-Interpretation
- Tunarr-DizqueTV-Integration
- Distribution
- Build-Progress
Operations
Changelog & Migration