REST API (/api/v1/)
ChannelWatch ships a versioned REST API at /api/v1/. New integrations should target the versioned endpoints documented here.
This reference covers the public operational routes used by ChannelWatch. Some settings and log routes still use their existing /api/ paths; do not invent a /api/v1/ alias for a route listed below.
Find an endpoint
Section titled “Find an endpoint”- DVRs and per-DVR data
- Discovery and maintenance windows
- Update Center
- Settings, backups, and debug tools
- Activity and delivery history
- Authentication and security
Authentication
Section titled “Authentication”Fresh installs choose secure login or trusted-network no-auth before DVR setup. Secure login uses a cookie-backed session and CSRF token. Upgraded installations can still use the legacy shared X-API-Key compatibility path.
X-API-Key: <api-key>If trusted-network no-auth is configured, or CW_DISABLE_AUTH=true temporarily overrides the configured mode, the API is accessible without credentials.
Session-authenticated writes also require the X-CSRF-Token returned by login. A viewer session cannot perform operator or admin actions. A legacy shared API key has full access and bypasses user-role boundaries; see Users and permissions before reusing one in an integration.
Keep credentials out of URLs and public logs. HTTP 401 usually means authentication is missing or expired; 403 can mean insufficient permission, a missing CSRF token, or a rejected request origin. Read the returned error rather than retrying a write repeatedly.
DVR endpoints
Section titled “DVR endpoints”These endpoints return data scoped to configured DVRs. Use GET /api/v1/dvrs to discover valid DVR IDs.
List all DVRs
Section titled “List all DVRs”GET /api/v1/dvrsReturns configured, non-deleted DVR records. The current response model is a small list item shape:
[ { "id": "dvr_abc12345", "name": "Living Room", "host": "192.168.1.100", "port": 8089, "enabled": true }]The list response sticks to the small id/name/host/port/enabled shape shown above.
DVR details
Section titled “DVR details”GET /api/v1/dvrs/{dvr_id}Returns a richer DVRStatus payload for one DVR. The current fields include:
id,name,host,portconnectedversion,version_compatible,version_warningdisk_usage_percent,disk_total_gb,disk_free_gblibrary_shows,library_movies,library_episodesmonitoring_status,monitoring_ready,monitoring_reasonfreshness_status,last_freshness_at,last_event_at,freshness_age_seconds,stale_threshold_seconds
Example response:
{ "id": "dvr_abc12345", "name": "Living Room", "host": "192.168.1.100", "port": 8089, "connected": true, "version": "2025.06.15", "version_compatible": true, "version_warning": null, "disk_usage_percent": 42, "disk_total_gb": 2000.0, "disk_free_gb": 1160.0, "library_shows": 85, "library_movies": 120, "library_episodes": 940, "monitoring_status": "healthy", "monitoring_ready": true, "monitoring_reason": null, "freshness_status": "fresh", "last_freshness_at": "2026-04-21T07:00:00+00:00", "last_event_at": "2026-04-21T06:59:45+00:00", "freshness_age_seconds": 15.0, "stale_threshold_seconds": 300}Per-DVR streams
Section titled “Per-DVR streams”GET /api/v1/dvrs/{dvr_id}/streamsReturns an aggregate stream snapshot for one DVR. The current payload shape is:
dvr_id,dvr_nametotalwatching- array of objects withdevice,channel, andimagerecording- array of objects withtitleanduntilsubtitleimage
Example response:
{ "dvr_id": "dvr_abc12345", "dvr_name": "Living Room", "total": 2, "watching": [ { "device": "Apple TV", "channel": "ESPN", "image": "https://example.invalid/image.jpg" } ], "recording": [ { "title": "Evening News", "until": "7:30 PM" } ], "subtitle": "1 watching, 1 recording", "image": "https://example.invalid/image.jpg"}This endpoint is not a session-ID-oriented stream list.
Per-DVR system info
Section titled “Per-DVR system info”GET /api/v1/dvrs/{dvr_id}/system-infoReturns current DVR status plus storage and library summary fields:
dvr_id,dvr_name,host,portconnectedversion,version_compatible,version_warningdisk_usage_percent,disk_usage_gb,disk_total_gb,disk_free_gb,disk_severitylibrary_shows,library_movies,library_episodes
It does not expose raw storage-path details.
Per-DVR activity history
Section titled “Per-DVR activity history”GET /api/v1/dvrs/{dvr_id}/activity-historyReturns activity history for one DVR. The route also supports pagination and filtering query parameters such as offset, limit, type, search, and sort.
Per-DVR upcoming recordings
Section titled “Per-DVR upcoming recordings”GET /api/v1/dvrs/{dvr_id}/recordings/upcomingReturns upcoming recordings for one DVR.
Per-DVR health
Section titled “Per-DVR health”GET /api/v1/dvrs/{dvr_id}/healthReturns the current DvrHealthResponse snapshot for one DVR. The current response fields are:
dvr_id,dvr_name,host,portconnectedversion,version_compatible,version_warningdisk_status,disk_free_gb,disk_total_gblast_checked,last_event_atlast_freshness_at,last_freshness_source,freshness_age_seconds,freshness_statusmonitoring_status,monitoring_ready,monitoring_reasonsession_state_size,recent_alert_rate
Example response:
{ "dvr_id": "dvr_abc12345", "dvr_name": "Living Room", "host": "192.168.1.100", "port": 8089, "connected": true, "version": "2025.06.15", "version_compatible": true, "version_warning": null, "disk_status": "normal", "disk_free_gb": 1160.0, "disk_total_gb": 2000.0, "last_checked": "2026-04-21T07:05:00+00:00", "last_event_at": "2026-04-21T06:59:45+00:00", "last_freshness_at": "2026-04-21T07:05:00+00:00", "last_freshness_source": "watchdog", "freshness_age_seconds": 15.0, "freshness_status": "fresh", "monitoring_status": "healthy", "monitoring_ready": true, "monitoring_reason": null, "session_state_size": 3, "recent_alert_rate": 4.0}Discovery endpoint
Section titled “Discovery endpoint”POST /api/v1/discovery/scanTriggers an mDNS scan and returns the discovery helper response:
{ "servers": [ { "host": "192.168.1.120", "port": 8089, "display_name_suggestion": "Basement DVR" } ], "manual_add_available": true, "message": null}When nothing new is found, servers is empty and message contains the reason or operator guidance.
This route is not a generic anonymous scan endpoint. It uses the backend auth path and, when RBAC is enabled, requires at least the operator role.
Maintenance windows endpoint
Section titled “Maintenance windows endpoint”GET /api/v1/maintenance-windowsReturns advisory gaps between known recording jobs for each enabled DVR. The endpoint accepts:
minimum_minutesfrom1to1440, defaulting to60daysfrom1to31, defaulting to7start_hourfrom0to23end_hourfrom0to24; a value earlier thanstart_hourcreates an overnight rangeweekdaysas comma-separated local weekdays, where Monday is0and Sunday is6dvr_idto limit the result to one configured DVR
Each DVR result includes its status, the last verified schedule boundary, and any safe windows before that boundary. Unreachable, empty, malformed, or unobserved schedule periods are never reported as free time.
See Maintenance Windows for the Dashboard controls and safety limits.
Update Center endpoints
Section titled “Update Center endpoints”These routes power Settings > Updates. They require admin access when auth is enabled. If CW_DISABLE_AUTH=true, anyone who can reach the UI can use them.
GET /api/v1/update/statusGET /api/v1/update/policyPUT /api/v1/update/policyPOST /api/v1/update/checkPOST /api/v1/update/applyPOST /api/v1/update/postponePOST /api/v1/update/retryGET /api/v1/update/jobs/{job_id}POST /api/v1/update/rollbackKey behaviors:
GET /api/v1/update/statusreturns the current app version, runtime ABI, last trusted manifest, last update job, rollback availability, and auth-disabled warning state.GETandPUT /api/v1/update/policyread or save the automatic/notify-only policy and local maintenance-window settings.POST /api/v1/update/checkfetches trusted public ChannelWatch release metadata. Scheduled policy checks can do this too.POST /api/v1/update/applyverifies the signed manifest, signed bundle, SHA256 digest, runtime ABI, settings schema, and bundle allowlist before activating a compatible app update.GET /api/v1/update/jobs/{job_id}returns the persisted last update operation when the ID matches.POST /api/v1/update/rollbackrestores the previous active bundle pointer, or falls back to the image app, then restarts ChannelWatch.
Image-required releases return a structured conflict instead of attempting an unsafe in-app update.
Settings and administrative tools
Section titled “Settings and administrative tools”| Route | Purpose | Required session role |
|---|---|---|
GET /api/settings |
Read the current settings document, with protected fields masked | Authenticated read, after setup |
POST /api/settings |
Validate and save settings, then signal a core reload | Operator or admin |
GET /api/v1/backup/download |
Download the full private backup ZIP | Admin |
POST /api/v1/backup/restore |
Validate an uploaded backup and restore it transactionally | Admin |
GET /api/v1/debug/bundle |
Download a sanitized troubleshooting bundle | Admin |
GET /api/logs |
Read a bounded recent log tail | Operator or admin |
GET /api/logs/download |
Download the application log | Operator or admin |
The settings write is a settings document, not a general JSON Patch endpoint. Preserve the current document and the target DVR’s id when changing a DVR credential. Replace only that entry’s api_key; the normal save path encrypts the protected value. Existing masked placeholders keep saved secrets rather than revealing them. Do not save a small partial document and assume omitted settings will remain unchanged.
A normal backup contains saved credentials and can include the matching encryption key. A debug bundle excludes the raw database and key. They are different exports; see Backup & Restore and Debug Bundles.
History and delivery records
Section titled “History and delivery records”| Route | Purpose |
|---|---|
GET /api/activity-history |
Paginated history with offset, limit, type, search, client, dvr_id, hours, and sort filters |
GET /api/v1/activity-history/filters |
Available activity/client filter values |
GET /api/v1/dvrs/{dvr_id}/activity-history |
History scoped to one DVR |
GET /api/v1/history/export |
Activity export used by Watch History |
GET /api/v1/notification-log |
Delivery rows with optional DVR, channel, status, and time filters |
History, per-DVR history, client facets and CSV export accept scope=watch to include only live TV, VOD and recording events. Omitting it keeps the full activity view, including DVR connection and disk events. The scope combines with the other filters before counting and pagination. Client facets default to the last 24 hours; use hours=0 for all retained activity, as Watch History does.
The notification log returns items, total, offset, and limit. Its page size defaults to 50 and is capped at 200. Activity and notification delivery are separate records: an activity row does not prove a receiver delivered the corresponding notification.
See Watch History and Notification Logs for the user-facing controls.
Auth and security endpoints
Section titled “Auth and security endpoints”The current backend exposes these auth and security routes:
POST /api/v1/auth/loginPOST /api/v1/auth/logoutGET /api/v1/auth/whoamiPOST /api/v1/auth/change-credentialsGET /api/v1/auth/setup-statusPOST /api/v1/auth/setupGET /api/v1/security/statusKey behaviors:
POST /api/v1/auth/loginreturns the authenticated username, role, and a CSRF token, and sets thechannelwatch_sessioncookie.POST /api/v1/auth/logoutinvalidates the current session and clears the cookie.GET /api/v1/auth/whoamireturns the current session identity when RBAC is enabled and the cookie is valid.POST /api/v1/auth/change-credentialslets the signed-in user change their own username or password.GET /api/v1/auth/setup-statusreports whether the fresh-install security choice is still required and listsrbacandnoneas available setup modes.POST /api/v1/auth/setupeither creates the first admin for secure login or persists the trusted-network no-auth choice.GET /api/v1/security/statusreports the current security mode, whether RBAC is enabled, whether the shared API key is configured, whether API-key fallback is active, whether session setup is still required, and whether stored per-DVR API keys are encrypted at rest.
The auth routes are auth-exempt so a first login or first-admin setup can happen without an existing session. They are still RBAC-aware: for example, POST /api/v1/auth/login returns 501 when RBAC is disabled.
Related pages
Section titled “Related pages”- Security modes - current auth and access behavior
- API Keys - create and manage API-key access
- Per-DVR Health - endpoint details for DVR health snapshots
- Health probes -
/api/healthand/healthz/*response semantics - Prometheus metrics - the current
/metricssurface - Maintenance Windows - advisory recording-schedule gaps and their limits
- Updating ChannelWatch - Update Center behavior and image-required releases