All API endpoints now provide consistent error responses with appropriate HTTP status codes.
{
"error": "Error Type",
"message": "Human-readable error message",
"path": "/api/endpoint",
"timestamp": "2025-12-30T20:00:00.000Z",
"details": "Additional error details (optional)"
}200 OK- Request successful400 Bad Request- Invalid request parameters or body404 Not Found- Resource or endpoint not found500 Internal Server Error- Unexpected server error502 Bad Gateway- Upstream service (M3U/EPG source) unavailable503 Service Unavailable- Service not ready (e.g., EPG not loaded)
404 Not Found:
{
"error": "Not Found",
"message": "The requested resource was not found",
"path": "/nonexistent",
"method": "GET"
}503 Service Unavailable:
{
"error": "EPG not loaded yet",
"message": "EPG not loaded yet",
"path": "/xmltv.xml",
"timestamp": "2025-12-30T20:00:00.000Z"
}500 Internal Server Error:
{
"error": "Internal Server Error",
"message": "An unexpected error occurred",
"path": "/lineup.m3u",
"timestamp": "2025-12-30T20:00:00.000Z"
}The server fully supports reverse proxy deployments with proper header forwarding.
The following headers are honored for base URL generation:
X-Forwarded-Proto- Protocol (http/https)X-Forwarded-Protocol- Alternative protocol headerX-Url-Scheme- Alternative protocol headerX-Forwarded-Ssl- Set to "on" for HTTPSX-Forwarded-Host- Original host header
Nginx:
location / {
proxy_pass http://localhost:34400;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}Apache:
<Location />
ProxyPass http://localhost:34400/
ProxyPassReverse http://localhost:34400/
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Host "%{HTTP_HOST}e"
</Location>Basic health check that returns 200 if the server is running.
Response:
{
"status": "ok",
"timestamp": "2025-12-30T20:00:00.000Z"
}Liveness probe for Kubernetes or Docker health checks. Indicates if the server process is running.
Response:
{
"status": "ok",
"message": "Server is alive",
"timestamp": "2025-12-30T20:00:00.000Z"
}Readiness probe that checks if the server is ready to handle requests. Returns 200 if ready, 503 if not ready.
Response (Ready):
{
"timestamp": "2025-12-30T20:00:00.000Z",
"ready": true,
"checks": {
"channels": {
"status": "ok",
"count": 42,
"available": true
},
"channelsFile": {
"status": "ok",
"size": 12345,
"modified": "2025-12-30T19:00:00.000Z"
}
}
}Response (Not Ready):
{
"timestamp": "2025-12-30T20:00:00.000Z",
"ready": false,
"checks": {
"channels": {
"status": "warning",
"count": 0,
"available": false,
"message": "No channels loaded"
},
"channelsFile": {
"status": "ok",
"size": 2,
"modified": "2025-12-30T19:00:00.000Z"
}
}
}Comprehensive system diagnostics endpoint that provides information about the current state of the IPTV proxy.
Response:
{
"timestamp": "2025-12-30T04:19:09.594Z",
"uptime": 41.195943614,
"channels": {
"total": 4,
"bySource": {
"TestSource": 3,
"OtherSource": 1
},
"mapped": 2,
"unmapped": 2,
"file": {
"size": 947,
"modified": "2025-12-30T04:13:25.480Z"
}
},
"sources": {
"m3u": {
"count": 2,
"configured": [
{
"name": "TestSource",
"type": "m3u",
"url": "http://example.com/playlist.m3u"
}
],
"status": {
"TestSource": {
"status": "success",
"lastUpdate": "2025-12-30T04:13:25.480Z",
"error": null
}
}
},
"epg": {
"count": 1,
"configured": [
{
"name": "TestSource",
"url": "http://example.com/epg.xml"
}
]
}
},
"mappings": {
"total": 5,
"channelsCovered": 2,
"channelsNotCovered": 2,
"coveragePercent": 50
},
"parsing": {
"lastUpdate": "2025-12-30T04:13:25.480Z",
"recentErrors": []
}
}Channel mappings are stored in SQLite. These endpoints remain available for compatibility and bulk import/export flows, but the primary channel authoring workflow is now the canonical/output-profile API surface.
Add or update a single channel mapping.
Request Body:
{
"key": "Channel Name",
"mapping": {
"name": "New Channel Name",
"tvg_id": "channel.id",
"number": "101",
"logo": "http://example.com/logo.png"
}
}Response:
{
"status": "saved",
"key": "Channel Name",
"mapping": {
"name": "New Channel Name",
"tvg_id": "channel.id",
"number": "101",
"logo": "http://example.com/logo.png"
}
}Remove a channel mapping by key. The key should be URL-encoded.
Example:
DELETE /api/mapping/Channel%20NameResponse:
{
"status": "deleted",
"key": "Channel Name"
}Add or update multiple channel mappings at once.
Request Body:
{
"mappings": {
"Channel A": {
"name": "Channel A HD",
"tvg_id": "a.1",
"number": "1"
},
"Channel B": {
"name": "Channel B HD",
"tvg_id": "b.2",
"number": "2"
}
}
}Response:
{
"status": "saved",
"count": 2
}The M3U playlist endpoint now supports filtering by source or group.
Query Parameters:
source- Filter channels by source name (e.g.,?source=TestSource)group- Filter channels by group-title (mapped to source name)
Examples:
# Get all channels from TestSource
GET /lineup.m3u?source=TestSource
# Get all channels from a specific group
GET /lineup.m3u?group=TestSourceThe XMLTV EPG endpoint now supports filtering by source or specific channel IDs.
Query Parameters:
source- Filter EPG data by source name (e.g.,?source=TestSource)channels- Comma-separated list of channel IDs to include (e.g.,?channels=test1,test2,demo1)
Examples:
# Get EPG data only for TestSource channels
GET /xmltv.xml?source=TestSource
# Get EPG data for specific channels
GET /xmltv.xml?channels=test1,test2,demo1The following endpoints remain available:
GET /lineup.json- JSON lineup for HDHomeRun compatibilityGET /channels- List all channelsGET /api/config/m3u- Get the legacy M3U compatibility view synthesized from sourcesGET /api/config/epg- Get the legacy EPG compatibility view synthesized from sourcesGET /api/config/app- Get app configuration from the SQLite-backed app settings storeGET /api/config/channel-map- Get the legacy compatibility mapping view from the SQLite-backed mapping storePUT /api/config/m3u- Update sources through the legacy M3U compatibility viewPUT /api/config/epg- Update source EPG URLs through the legacy EPG compatibility viewPUT /api/config/app- Update app configuration in the SQLite-backed app settings storePUT /api/config/channel-map- Replace the legacy compatibility mapping view in the SQLite-backed mapping storePOST /api/reload/channels- Reload channels from sourcesPOST /api/reload/epg- Reload EPG dataGET /api/channel-health- Get channel health statusPOST /api/channel-health/run- Run channel health checkGET /api/mapping/candidates- Get legacy mapping candidatesGET /api/mapping/unmapped- Get legacy unmapped-channel suggestionsGET /api/mapping/conflicts- Get legacy mapping conflictsGET /api/canonical/channels- List canonical channels derived from mapped source channelsGET /api/canonical/bindings- List source-to-canonical channel bindingsGET /api/canonical/guide-bindings- List guide bindings between canonical channels and source EPG idsPATCH /api/canonical/channels/:id- Update canonical channel publish statePATCH /api/canonical/channels/:id/preferred-stream- Choose the preferred bound source stream for a canonical channelPATCH /api/canonical/channels/:id/guide-binding- Choose the preferred guide source and EPG channel id for a canonical channelGET /api/output-profiles- List output profilesGET /api/output-profiles/:slug/channels- List published channels for an output profileGET /api/output-profiles/:slug/entries- List editable output profile channel entries, including disabled channels and overridesPATCH /api/output-profiles/:slug/channels- Update output profile channel order, enabled state, and guide number overrides
All endpoints now include proper error handling and will return appropriate HTTP status codes with detailed error messages.
The IPTV Proxy includes a sophisticated caching system to improve performance and reduce load on source servers.
Get statistics for all caches.
Response:
{
"caches": {
"epg": {
"name": "epg",
"size": 5,
"ttl": 21600000,
"hits": 150,
"misses": 10,
"hitRate": "93.75%",
"entries": [
{
"key": "https://example.com|source:|channels:",
"age": 3600,
"ttlRemaining": 18000,
"expired": false
}
]
},
"m3u": {
"name": "m3u",
"size": 3,
"ttl": 3600000,
"hits": 200,
"misses": 5,
"hitRate": "97.56%",
"entries": []
}
},
"timestamp": "2026-01-11T03:00:00.000Z"
}Clear all caches.
Response:
{
"status": "success",
"message": "All caches cleared",
"timestamp": "2026-01-11T03:00:00.000Z"
}Clear a specific cache by name.
URL Parameters:
name- Cache name (e.g.,epg,m3u,lineup-json)
Response:
{
"status": "success",
"message": "Cache 'epg' cleared",
"timestamp": "2026-01-11T03:00:00.000Z"
}Update the TTL for a specific cache.
URL Parameters:
name- Cache name
Request Body:
{
"ttl": 7200
}Note: TTL is in seconds.
Response:
{
"status": "success",
"message": "Cache 'epg' TTL updated",
"name": "epg",
"ttl": 7200,
"timestamp": "2026-01-11T03:00:00.000Z"
}The Preview API allows you to test configuration changes before saving them.
Preview merged M3U playlist with temporary configuration.
Request Body:
{
"m3uConfig": {
"urls": [
{
"name": "Test Source",
"url": "https://example.com/playlist.m3u"
}
]
},
"channelMapConfig": {
"Channel Name": {
"number": "100",
"tvg_id": "channel-id"
}
}
}Response:
Returns an M3U playlist file with Content-Type: application/x-mpegURL.
Preview merged channels as JSON with temporary configuration.
Request Body:
{
"m3uConfig": {
"urls": [
{
"name": "Test Source",
"url": "https://example.com/playlist.m3u"
}
]
},
"channelMapConfig": {
"Channel Name": {
"number": "100"
}
}
}Response:
{
"channels": [
{
"name": "Channel Name",
"tvg_id": "channel-id",
"logo": "https://example.com/logo.png",
"url": "https://example.com/stream",
"guideNumber": "100",
"source": "Test Source"
}
],
"count": 1,
"sources": ["Test Source"]
}Preview merged EPG with temporary configuration.
Request Body:
{
"epgConfig": {
"urls": [
{
"name": "Test EPG",
"url": "https://example.com/xmltv.xml"
}
]
},
"channels": [
{
"name": "Channel Name",
"tvg_id": "channel-id",
"source": "Test EPG"
}
]
}Response:
Returns an XMLTV file with Content-Type: application/xml.
Preview merged EPG as JSON with temporary configuration.
Request Body:
{
"epgConfig": {
"urls": [
{
"name": "Test EPG",
"url": "https://example.com/xmltv.xml"
}
]
},
"channels": [
{
"name": "Channel Name",
"tvg_id": "channel-id",
"source": "Test EPG"
}
]
}Response:
{
"channels": 50,
"programmes": 1000,
"sources": ["Test EPG"],
"data": {
"tv": {
"channel": [],
"programme": []
}
}
}All endpoints require authentication (session cookie or equivalent).
Create a timestamped snapshot of the active SQLite database plus exported compatibility files. Backups are stored under data/backups/. providers.yaml, app.yaml, and channel-map.yaml are always included when present; the SQLite database snapshot is included when available; legacy m3u.yaml and epg.yaml are included only if they still exist for compatibility.
Response:
{
"status": "created",
"name": "backup-2026-01-01T12-00-00",
"files": ["iptv-proxy.sqlite", "providers.yaml", "app.yaml", "channel-map.yaml"]
}List all available backups (newest first).
Response:
{
"backups": [{ "name": "backup-2026-01-01T12-00-00" }],
"count": 1
}Restore application state from the named backup. When a SQLite snapshot is present, it becomes the restored source of truth and the compatibility exports are regenerated from it.
URL Parameters:
name- Backup name (e.g.backup-2026-01-01T12-00-00)
Response:
{
"status": "restored",
"name": "backup-2026-01-01T12-00-00",
"files": ["providers.yaml", "app.yaml"]
}Error responses:
400– Invalid backup name (name fails validation)404– Backup not found
Delete the named backup directory.
URL Parameters:
name- Backup name
Response:
{
"status": "deleted",
"name": "backup-2026-01-01T12-00-00"
}Return recently completed stream sessions in reverse-chronological order. Requires authentication. The server keeps up to the last 100 sessions in memory.
Response:
{
"history": [
{
"ip": "192.168.1.10",
"channelId": "23.1",
"name": "PBS",
"tvg_id": "PBS",
"startedAt": "2026-01-01T12:00:00.000Z",
"lastSeen": "2026-01-01T12:44:50.000Z",
"endedAt": "2026-01-01T12:45:00.000Z",
"durationSeconds": 2700
}
],
"count": 1
}