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.

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.

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
POST /api/v1/update/check
POST /api/v1/update/apply
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.
  • POST /api/v1/update/check fetches the trusted public ChannelWatch update manifest.
  • 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.

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.