Skip to content

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.

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.

These endpoints return data scoped to configured DVRs. Use GET /api/v1/dvrs to discover valid DVR IDs.

GET /api/v1/dvrs

Returns 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.

GET /api/v1/dvrs/{dvr_id}

Returns a richer DVRStatus payload for one DVR. The current fields include:

  • id, name, host, port
  • connected
  • version, version_compatible, version_warning
  • disk_usage_percent, disk_total_gb, disk_free_gb
  • library_shows, library_movies, library_episodes
  • monitoring_status, monitoring_ready, monitoring_reason
  • freshness_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
}
GET /api/v1/dvrs/{dvr_id}/streams

Returns an aggregate stream snapshot for one DVR. The current payload shape is:

  • dvr_id, dvr_name
  • total
  • watching - array of objects with device, channel, and image
  • recording - array of objects with title and until
  • subtitle
  • image

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.

GET /api/v1/dvrs/{dvr_id}/system-info

Returns current DVR status plus storage and library summary fields:

  • dvr_id, dvr_name, host, port
  • connected
  • version, version_compatible, version_warning
  • disk_usage_percent, disk_usage_gb, disk_total_gb, disk_free_gb, disk_severity
  • library_shows, library_movies, library_episodes

It does not expose raw storage-path details.

GET /api/v1/dvrs/{dvr_id}/activity-history

Returns activity history for one DVR. The route also supports pagination and filtering query parameters such as offset, limit, type, search, and sort.

GET /api/v1/dvrs/{dvr_id}/recordings/upcoming

Returns upcoming recordings for one DVR.

GET /api/v1/dvrs/{dvr_id}/health

Returns the current DvrHealthResponse snapshot for one DVR. The current response fields are:

  • dvr_id, dvr_name, host, port
  • connected
  • version, version_compatible, version_warning
  • disk_status, disk_free_gb, disk_total_gb
  • last_checked, last_event_at
  • last_freshness_at, last_freshness_source, freshness_age_seconds, freshness_status
  • monitoring_status, monitoring_ready, monitoring_reason
  • session_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
}
POST /api/v1/discovery/scan

Triggers 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.

GET /api/v1/maintenance-windows

Returns advisory gaps between known recording jobs for each enabled DVR. The endpoint accepts:

  • minimum_minutes from 1 to 1440, defaulting to 60
  • days from 1 to 31, defaulting to 7
  • start_hour from 0 to 23
  • end_hour from 0 to 24; a value earlier than start_hour creates an overnight range
  • weekdays as comma-separated local weekdays, where Monday is 0 and Sunday is 6
  • dvr_id to 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.

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/status
GET /api/v1/update/policy
PUT /api/v1/update/policy
POST /api/v1/update/check
POST /api/v1/update/apply
POST /api/v1/update/postpone
POST /api/v1/update/retry
GET /api/v1/update/jobs/{job_id}
POST /api/v1/update/rollback

Key behaviors:

  • GET /api/v1/update/status returns the current app version, runtime ABI, last trusted manifest, last update job, rollback availability, and auth-disabled warning state.
  • GET and PUT /api/v1/update/policy read or save the automatic/notify-only policy and local maintenance-window settings.
  • POST /api/v1/update/check fetches trusted public ChannelWatch release metadata. Scheduled policy checks can do this too.
  • POST /api/v1/update/apply verifies 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/rollback restores 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.

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.

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.

The current backend exposes these auth and security routes:

POST /api/v1/auth/login
POST /api/v1/auth/logout
GET /api/v1/auth/whoami
POST /api/v1/auth/change-credentials
GET /api/v1/auth/setup-status
POST /api/v1/auth/setup
GET /api/v1/security/status

Key behaviors:

  • POST /api/v1/auth/login returns the authenticated username, role, and a CSRF token, and sets the channelwatch_session cookie.
  • POST /api/v1/auth/logout invalidates the current session and clears the cookie.
  • GET /api/v1/auth/whoami returns the current session identity when RBAC is enabled and the cookie is valid.
  • POST /api/v1/auth/change-credentials lets the signed-in user change their own username or password.
  • GET /api/v1/auth/setup-status reports whether the fresh-install security choice is still required and lists rbac and none as available setup modes.
  • POST /api/v1/auth/setup either creates the first admin for secure login or persists the trusted-network no-auth choice.
  • GET /api/v1/security/status reports 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.