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.

When auth is enabled, requests can be authorized with the shared X-API-Key header. When RBAC is enabled, the backend also ships session-based auth endpoints under /api/v1/auth/*.

X-API-Key: <your-api-key>

If auth is disabled (CW_DISABLE_AUTH=true), 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 source-backed 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 a source-backed 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.

The current backend exposes these auth and security routes:

POST /api/v1/auth/login
POST /api/v1/auth/logout
GET /api/v1/auth/whoami
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.
  • GET /api/v1/auth/setup-status reports whether RBAC still needs the first admin account.
  • POST /api/v1/auth/setup creates the first admin account when RBAC is enabled and no users exist yet.
  • 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.