All API endpoints are accessible at http://localhost:3000/api/
GET /api/stream-checker/status
Returns service status, statistics, and queue information.
Response:
{
"running": true,
"current_channel": "Channel Name",
"queue_size": 5,
"statistics": {
"total_checked": 150,
"total_failed": 3,
"total_improved": 120
}
}POST /api/stream-checker/start
Starts the stream checking service.
POST /api/stream-checker/stop
Stops the stream checking service.
GET /api/stream-checker/queue
Returns current queue of channels pending check.
POST /api/stream-checker/queue/add
Content-Type: application/json
{
"channel_ids": [1, 2, 3]
}
Adds specific channels to the checking queue.
POST /api/stream-checker/queue/clear
Removes all pending checks from the queue.
GET /api/stream-checker/config
Returns current stream checker configuration.
PUT /api/stream-checker/config
Content-Type: application/json
{
"enabled": true,
"global_check_schedule": {
"enabled": true,
"frequency": "daily",
"hour": 3,
"minute": 0
},
"queue": {
"check_on_update": true,
"max_channels_per_run": 50
},
"scoring": {
"weights": {
"bitrate": 0.30,
"resolution": 0.25,
"fps": 0.15,
"codec": 0.10,
"errors": 0.20
}
}
}
Updates stream checker configuration.
Configuration Options:
enabled- Enable/disable the stream checker serviceglobal_check_schedule.enabled- Enable scheduled global checks of all channelsglobal_check_schedule.frequency- Schedule frequency ('daily' or 'monthly')global_check_schedule.hour- Hour to run check (0-23)global_check_schedule.minute- Minute to run check (0-59)queue.check_on_update- Automatically queue channels for checking when M3U playlists are updatedqueue.max_channels_per_run- Maximum number of channels to check per runscoring.weights- Weights for different quality factors in stream scoring
GET /api/stream-checker/progress
Returns real-time progress of current check operation.
POST /api/stream-checker/check-channel
Content-Type: application/json
{
"channel_id": 123
}
Queues a specific channel for checking with high priority.
Response:
{
"message": "Channel 123 queued for immediate checking"
}POST /api/stream-checker/check-single-channel
Content-Type: application/json
{
"channel_id": 123
}
Performs a complete channel refresh similar to a global check but scoped to a single channel. This operation:
- Refreshes M3U playlists for accounts associated with the channel
- Re-discovers and assigns matching streams (including previously dead ones)
- Force checks all streams, bypassing the 2-hour immunity period
- Revives streams that are now working
- Removes streams that are still dead
This is a synchronous operation that returns detailed results when complete.
Response:
{
"success": true,
"channel_id": 123,
"channel_name": "Example Channel",
"stats": {
"total_streams": 15,
"dead_streams": 2,
"avg_resolution": "1920x1080",
"avg_bitrate": "5000 kbps",
"stream_details": [
{
"stream_id": 1001,
"stream_name": "Stream 1",
"resolution": "1920x1080",
"bitrate": "6000 kbps",
"video_codec": "h264",
"fps": "30.0"
}
]
}
}Note: This endpoint performs the check immediately and waits for completion, while /check-channel queues the check and returns immediately.
POST /api/stream-checker/mark-updated
Content-Type: application/json
{
"channel_ids": [1, 2, 3]
}
Marks channels as updated and needing check.
GET /api/automation/status
Returns automation service status and configuration.
POST /api/automation/start
Starts the automation service.
POST /api/automation/stop
Stops the automation service.
GET /api/automation/config
Returns automation configuration.
PUT /api/automation/config
Content-Type: application/json
{
"playlist_update_interval_minutes": 5,
"autostart_automation": false,
"enabled_m3u_accounts": [],
"enabled_features": {
"auto_playlist_update": true,
"auto_stream_discovery": true,
"changelog_tracking": true
}
}
Updates automation configuration.
Configuration Options:
playlist_update_interval_minutes- How often to check for playlist updatesautostart_automation- Whether to automatically start the automation service on server startupenabled_m3u_accounts- Array of M3U account IDs to enable (empty array means all accounts)enabled_features.auto_playlist_update- Enable automatic playlist updatesenabled_features.auto_stream_discovery- Enable automatic stream discovery via regexenabled_features.changelog_tracking- Track changes in the changelog
POST /api/automation/discover-streams
Manually triggers stream discovery cycle.
GET /api/channels
Returns list of all channels.
Query Parameters:
page- Page number (default: 1)per_page- Results per page (default: 50)
GET /api/channels/{channel_id}
Returns details for a specific channel.
GET /api/channels/{channel_id}/stats
Returns comprehensive statistics for a specific channel.
Response:
{
"channel_id": "123",
"channel_name": "Example Channel",
"logo_id": 456,
"total_streams": 25,
"dead_streams": 2,
"most_common_resolution": "1920x1080",
"average_bitrate": 8500000,
"resolutions": {
"1920x1080": 18,
"1280x720": 7
}
}Statistics include:
total_streams- Count of all streams for the channeldead_streams- Count of non-functional streamsmost_common_resolution- Most frequently occurring resolutionaverage_bitrate- Average bitrate across all streams (in bps)resolutions- Distribution of resolutions across streams
GET /api/channels/{channel_id}/streams
Returns all streams for a specific channel.
GET /api/regex-patterns
Returns all configured regex patterns.
POST /api/regex-patterns
Content-Type: application/json
{
"pattern": "^HD.*Sports$",
"channel_id": 123,
"enabled": true
}
Adds a new regex pattern.
PUT /api/regex-patterns/{pattern_id}
Content-Type: application/json
{
"pattern": "^HD.*Sports$",
"channel_id": 123,
"enabled": true
}
Updates an existing pattern.
DELETE /api/regex-patterns/{pattern_id}
Deletes a regex pattern.
POST /api/regex-patterns/import
Content-Type: application/json
{
"patterns": {
"1": {
"name": "CNN",
"regex": [".*CNN.*"],
"enabled": true
},
"2": {
"name": "ESPN",
"regex_patterns": [
{
"pattern": ".*ESPN.*",
"m3u_accounts": [11, 13],
"priority": 0
}
],
"enabled": true
}
},
"global_settings": {
"case_sensitive": false
}
}
Imports regex patterns from a JSON file.
Supported Formats:
- Old format: Uses
"regex": ["pattern1", "pattern2"]field (backward compatible) - New format: Uses
"regex_patterns": [{"pattern": "p1", "m3u_accounts": [...], "priority": 0}]field - Mixed format: Both old and new formats can coexist in the same import file
Notes:
- The import overwrites existing patterns
- Patterns are validated before import
- Empty pattern lists are rejected
- Old format files without
m3u_accountsandpriorityfields are automatically migrated to the new format
POST /api/regex-patterns/test
Content-Type: application/json
{
"pattern": "^HD.*Sports$"
}
Tests a regex pattern against available streams.
GET /api/changelog?days=7
Returns activity history with structured entries.
Query Parameters:
days- Number of days to retrieve (default: 7, options: 1, 7, 30, 90)
Response:
[
{
"timestamp": "2024-01-15T10:30:00.000Z",
"action": "single_channel_check",
"details": {
"channel_id": 123,
"channel_name": "Example Channel",
"total_streams": 15,
"dead_streams": 2,
"avg_resolution": "1920x1080",
"avg_bitrate": "5000 kbps"
},
"subentries": [
{
"group": "check",
"items": [
{
"type": "check",
"channel_id": 123,
"channel_name": "Example Channel",
"stats": {
"total_streams": 15,
"dead_streams": 2,
"avg_resolution": "1920x1080",
"avg_bitrate": "5000 kbps",
"stream_details": [...]
}
}
]
}
]
}
]Action Types:
playlist_update_match- Playlist update with stream matching and channel checksglobal_check- Scheduled global check of all channelssingle_channel_check- Individual channel checkplaylist_refresh- M3U playlist refresh (legacy)streams_assigned- Stream assignment to channels (legacy)
Subentry Groups:
update_match- Streams added to channels during playlist updatescheck- Channel check results with statistics
Note: The changelog endpoint merges entries from both the automation manager and stream checker, sorted by timestamp (newest first).
GET /api/health
Returns service health status.
Response:
{
"status": "healthy",
"version": "1.0.0",
"services": {
"automation": "running",
"stream_checker": "running"
}
}Auto-create rules automatically create scheduled events when EPG programs match regex patterns.
GET /api/scheduling/auto-create-rules
Get all auto-create rules.
Response:
[
{
"id": "rule-uuid",
"name": "Breaking News",
"channel_id": 123,
"channel_name": "CNN HD",
"channel_logo_url": "http://...",
"tvg_id": "cnn.us",
"regex_pattern": "^Breaking News",
"minutes_before": 5,
"created_at": "2024-01-01T12:00:00+00:00"
}
]POST /api/scheduling/auto-create-rules
Content-Type: application/json
{
"name": "Breaking News Alert",
"channel_id": 123,
"regex_pattern": "^Breaking News",
"minutes_before": 5
}
Create a new auto-create rule. The system will immediately match existing EPG programs to the new rule.
Response: Returns the created rule object (201 Created)
PUT /api/scheduling/auto-create-rules/{rule_id}
Content-Type: application/json
{
"name": "Updated Rule Name",
"channel_id": 123,
"regex_pattern": "^Updated Pattern",
"minutes_before": 10
}
Update an existing auto-create rule. All fields are optional - only provided fields will be updated. Existing scheduled events created by this rule will be deleted and new ones will be created based on the updated rule.
Response: Returns the updated rule object (200 OK)
DELETE /api/scheduling/auto-create-rules/{rule_id}
Delete an auto-create rule and all scheduled events created by it.
Response:
{
"message": "Rule deleted"
}POST /api/scheduling/auto-create-rules/test
Content-Type: application/json
{
"channel_id": 123,
"regex_pattern": "^Breaking News"
}
Test a regex pattern against current EPG programs for a channel without creating a rule.
Response:
{
"matches": [
{
"title": "Breaking News: Market Update",
"start_time": "2024-01-01T14:00:00+00:00",
"end_time": "2024-01-01T14:30:00+00:00"
}
]
}GET /api/scheduling/auto-create-rules/export
Export all auto-create rules as JSON. The exported rules contain only essential fields (name, channel_ids, regex_pattern, minutes_before) and can be imported back using the import endpoint.
Response:
[
{
"name": "Breaking News",
"channel_ids": [123, 456],
"channel_id": 123,
"regex_pattern": "^Breaking News",
"minutes_before": 5
}
]POST /api/scheduling/auto-create-rules/import
Content-Type: application/json
[
{
"name": "Breaking News",
"channel_ids": [123, 456],
"regex_pattern": "^Breaking News",
"minutes_before": 5
},
{
"name": "Sports Events",
"channel_id": 789,
"regex_pattern": "^Live:|Championship",
"minutes_before": 10
}
]
Import auto-create rules from JSON. Each rule is validated before import. Invalid rules are skipped and reported in the response.
Request Fields:
name(required): Rule namechannel_ids(required, unlesschannel_idprovided): Array of channel IDschannel_id(optional): Single channel ID for backward compatibilityregex_pattern(required): Regex pattern to match programsminutes_before(optional, default: 5): Minutes before program start
Response:
{
"imported": 2,
"failed": 0,
"total": 2,
"errors": []
}If some rules fail:
{
"imported": 1,
"failed": 1,
"total": 2,
"errors": [
"Rule 2 ('Sports Events'): Channel 789 not found"
]
}GET /api/scheduling/processor/status
Get the status of the background thread that processes scheduled EPG events.
Response:
{
"running": true,
"thread_alive": true
}POST /api/scheduling/processor/start
Start the background thread for processing scheduled events. The processor checks for due events every 30 seconds using an event-based approach for better responsiveness. When new events are created, the processor wakes up immediately to check them.
Response:
{
"message": "Scheduled event processor started"
}Note: The processor is automatically started when the Flask app starts (if wizard is complete).
POST /api/scheduling/processor/stop
Stop the background thread for processing scheduled events.
Response:
{
"message": "Scheduled event processor stopped"
}POST /api/scheduling/process-due-events
Manually trigger processing of all scheduled events that are due. This is handled automatically by the background thread, but can be called manually if needed.
Response:
{
"message": "Processed 2 event(s), 2 successful",
"processed": 2,
"successful": 2,
"results": [
{
"event_id": "abc123",
"channel_name": "ESPN HD",
"program_title": "Monday Night Football",
"success": true
}
]
}How It Works:
- The background thread checks for due scheduled events every 30 seconds using an event-based approach
- When an event is created, the thread is woken up immediately to check for new due events
- When an event is due (current time >= check_time), it executes a channel check
- The channel check includes the program name in the changelog entry
- After successful execution, the event is automatically deleted from the schedule
- Any errors are logged but don't stop the processor
Architecture:
- Runs as a daemon thread in the Flask application
- Uses threading.Event for responsive wake-up (inspired by Global Action scheduler)
- Thread-safe using locks in SchedulingService
- Survives Flask reloads in development mode (daemon thread)
- Automatically starts with Flask app (when wizard is complete)
- Check interval reduced from 60s to 30s for better responsiveness
GET /api/scheduling/epg-refresh/status
Get the status of the background thread that periodically refreshes EPG data and creates scheduled events from auto-create rules.
Response:
{
"running": true,
"thread_alive": true
}POST /api/scheduling/epg-refresh/start
Start the background thread for periodic EPG refresh. The processor fetches EPG data based on the configured refresh interval (default: 60 minutes, minimum: 5 minutes) and automatically matches programs to auto-create rules.
Response:
{
"message": "EPG refresh processor started"
}Note: The processor is automatically started when the Flask app starts (if wizard is complete).
POST /api/scheduling/epg-refresh/stop
Stop the background thread for EPG refresh.
Response:
{
"message": "EPG refresh processor stopped"
}POST /api/scheduling/epg-refresh/trigger
Manually trigger an immediate EPG refresh, bypassing the configured interval. The processor will wake up and fetch EPG data immediately.
Response:
{
"message": "EPG refresh triggered"
}How It Works:
- The background thread starts 5 seconds after application startup
- It fetches EPG data from Dispatcharr's
/api/epg/grid/endpoint - The
match_programs_to_rules()function is called automatically - Scheduled events are created for programs matching auto-create rules
- The processor waits for the configured interval before the next refresh
- On error, it waits 5 minutes before retrying
Configuration:
- Refresh interval configured via
epg_refresh_interval_minutesin scheduling config - Default: 60 minutes
- Minimum: 5 minutes
- Can be adjusted dynamically via configuration update
Architecture:
- Runs as a daemon thread in the Flask application
- Uses threading.Event for responsive wake-up
- Constants defined for timeouts and delays
- Critical error handling with graceful shutdown
- Automatically starts with Flask app (when wizard is complete)
GET /api/profiles
Get all available channel profiles from Dispatcharr.
Response:
[
{
"id": 1,
"name": "Family Profile",
"channels": "..."
},
{
"id": 2,
"name": "Sports Profile",
"channels": "..."
}
]POST /api/profiles/refresh
Force a refresh of channel profiles from Dispatcharr API. Use this when profiles are not appearing or to fetch newly created profiles.
Response:
{
"success": true,
"message": "Successfully refreshed 3 channel profiles",
"profile_count": 3,
"profiles": [...]
}Error Response:
{
"success": false,
"message": "Failed to refresh channel profiles"
}GET /api/profiles/diagnose
Get detailed diagnostic information about profile fetching status. Useful for troubleshooting when profiles are not appearing.
Response:
{
"udi_initialized": true,
"dispatcharr_configured": true,
"dispatcharr_base_url": "http://dispatcharr:9191",
"cache_profile_count": 0,
"storage_profile_count": 0,
"last_refresh_time": null,
"profiles_in_cache": [],
"diagnosis": "No profiles found",
"possible_causes": [
"No channel profiles have been created in Dispatcharr yet",
"Profile fetch failed during initialization",
"Authentication issue preventing API access",
"Network connectivity problem"
],
"recommended_actions": [
"Create channel profiles in Dispatcharr web UI (Channels > Profiles)",
"Click 'Refresh Profiles' button to force a refresh",
"Check Dispatcharr logs for errors",
"Verify DISPATCHARR_BASE_URL, DISPATCHARR_USER, and DISPATCHARR_PASS in .env"
]
}GET /api/profiles/{profile_id}/channels
Get channels for a specific profile from Dispatcharr, including their enabled/disabled status.
Response:
{
"profile": {
"id": 1,
"name": "Family Profile",
"channels": "..."
},
"channels": [
{
"channel_id": 1,
"enabled": true
},
{
"channel_id": 2,
"enabled": false
},
{
"channel_id": 3,
"enabled": true
}
]
}Notes:
- The
channelsarray contains channel-profile associations - Each channel object has
channel_idandenabledproperties - The frontend uses this to filter which channels to display when a profile filter is active
- If Dispatcharr's profile data cannot be parsed, falls back to returning all channels as enabled
POST /api/profiles/{profile_id}/snapshot
Create a snapshot of the current channels in a profile. This records which channels should be in the profile for later re-enabling.
Response:
{
"message": "Snapshot created successfully",
"snapshot": {
"profile_id": 1,
"profile_name": "Family Profile",
"channel_ids": [1, 2, 3, 4, 5],
"created_at": "2025-12-16T12:00:00",
"channel_count": 5
}
}GET /api/profiles/{profile_id}/snapshot
Retrieve the snapshot for a specific profile.
Response:
{
"profile_id": 1,
"profile_name": "Family Profile",
"channel_ids": [1, 2, 3, 4, 5],
"created_at": "2025-12-16T12:00:00",
"channel_count": 5
}DELETE /api/profiles/{profile_id}/snapshot
Delete the snapshot for a specific profile.
Response:
{
"message": "Snapshot deleted successfully"
}GET /api/profiles/snapshots
Get all profile snapshots.
Response:
{
"1": {
"profile_id": 1,
"profile_name": "Family Profile",
"channel_ids": [1, 2, 3, 4, 5],
"created_at": "2025-12-16T12:00:00",
"channel_count": 5
},
"2": {
"profile_id": 2,
"profile_name": "Sports Profile",
"channel_ids": [10, 11, 12],
"created_at": "2025-12-16T13:00:00",
"channel_count": 3
}
}POST /api/profiles/{profile_id}/disable-empty-channels
Disable channels with no working streams in a specific profile.
Response:
{
"message": "Disabled 3 empty channels",
"disabled_count": 3,
"total_checked": 50
}How It Works:
- Fetches all channels from UDI cache
- For each channel, checks if all associated streams are marked as dead
- Channels with no streams or all dead streams are considered "empty"
- Uses Dispatcharr API to disable empty channels in the target profile
- Preserves channel data - only changes enabled/disabled status in profile
See CHANNEL_PROFILES_FEATURE.md for complete documentation and troubleshooting guide.