API

The API is JSON over HTTP. Guarded routes require either:

  • x-obbystreams-token: <token>
  • obbystreams_token=<token> cookie

Tokens come from dashboard.session_token in the YAML config.

Login

POST /api/auth/login
content-type: application/json

{"password":"dashboard-password"}

Success:

{"ok":true,"token":"configured-session-token"}

The response also sets obbystreams_token.

Health

GET /api/health

No auth is required.

Successful ready response:

{
  "ok": true,
  "ready": true,
  "checks": {
    "managed_process": true,
    "links_configured": true,
    "playlist_ready": true,
    "playlist_fresh": true
  },
  "health": {
    "state": "healthy",
    "level": "ok",
    "message": "Stream is producing fresh HLS output."
  }
}

The endpoint returns 503 when checks fail.

Status

GET /api/status
x-obbystreams-token: <token>

Returns:

  • sanitized config
  • managed process metrics
  • existing unmanaged stream processes
  • HLS metrics
  • health assessment
  • recent events
  • recent logs
  • recent errors
  • runtime counters

Config

Read sanitized config:

GET /api/config
x-obbystreams-token: <token>

Update stream config:

PUT /api/config
content-type: application/json
x-obbystreams-token: <token>

{
  "encoder": "auto",
  "bitrate": "6M",
  "audio_bitrate": "192k",
  "public_hls_url": "https://live.example/stream/ufc.m3u8",
  "links": ["https://example.com/live.m3u8"]
}

Accepted keys:

  • links
  • encoder
  • bitrate
  • audio_bitrate
  • output_dir
  • public_hls_url
  • auto_recover
  • auto_restart_on_exit
  • watchdog_restart_cooldown
  • startup_grace_seconds
  • playlist_stale_seconds

Some changes restart a running stream automatically.

links are now a compatibility view over structured stream sources. New cockpit features should use source endpoints.

Add:

POST /api/links
content-type: application/json
x-obbystreams-token: <token>

{"url":"https://example.com/live.m3u8"}

Remove:

POST /api/links/remove
content-type: application/json
x-obbystreams-token: <token>

{"url":"https://example.com/live.m3u8"}

Links must be HTTP(S), are deduplicated, and are normalized before being written to config.

Sources

Sources are guarded cockpit records with stable IDs, labels, type, URL, enabled state, and optional private headers. Headers are used by the proxy/ffmpeg path and are not returned by public endpoints.

List configured source status:

GET /api/sources
x-obbystreams-token: <token>

Switch the running cockpit preference to a source:

POST /api/sources/activate
content-type: application/json
x-obbystreams-token: <token>

{"id":"source-2"}

Recover a sour-signal source by scraping a replacement playlist:

POST /api/sources/recover-soursignal
content-type: application/json
x-obbystreams-token: <token>

{"id":"sour-signal-main"}

Private IPTV Automation

Private IPTV routes are guarded cockpit routes. They manage automatic discovery of the official/private ffmpeg source from the authenticated provider playlist. They do not create public watcher sources.

Read runtime state:

GET /api/private-iptv
x-obbystreams-token: <token>

Force an immediate refresh:

POST /api/private-iptv/refresh
content-type: application/json
x-obbystreams-token: <token>

{}

The refresh flow:

  1. Fetches private_iptv.provider_url or uses private_iptv.playlist_url.
  2. Parses M3U #EXTINF rows.
  3. Scores UFC/MMA/fight-day candidates and rejects placeholders such as “No Scheduled Event”, 24/7, replay, and stale rows.
  4. Probes candidate playback for bad-but-valid responses: HTML block pages, empty playlists, dead variants, ended tiny windows, and unreadable segments.
  5. Writes accepted private entries to stream.sources using the configured auto-source prefix.
  6. Disables auto-created private candidates when inactive. Managed ffmpeg stays live by default unless keep_stream_live_when_inactive is explicitly false.

Private sour-signal playback probes are connection-budget aware. POST /api/private-iptv/refresh respects the budget unless the body includes {"force_probe": true}.

Provider cookies and sensitive headers are redacted from config/status responses.

Public watcher endpoints:

GET /api/public-configured-sources
GET /api/public-streams
GET /api/news
GET /api/live
GET /api/viewers
POST /api/viewers

/api/public-configured-sources returns the synthetic server-1 managed source for the official ffmpeg output.

/api/public-streams returns separately managed pasted public internet streams. Each item includes url plus a CORS-safe playback_url that points through /api/proxy-hls. These records are not official ffmpeg sources and do not affect stream.sources. Public records may carry server-side headers such as Referer, Origin, or User-Agent; the proxy applies those headers while keeping browser playback on s.obby.ca.

/api/news returns visible ObbyWatcher news/changelog entries managed by the cockpit. Entries are plain text fields with title, body, tone, pinned, timestamps, and optional link_url; hidden entries stay available in cockpit config but are excluded from the public endpoint.

Operator-only news management:

POST /api/news
content-type: application/json
x-obbystreams-token: <token>

{"title":"Main card live","body":"Use Server 1 first; public backups are available.","tone":"info","visible":true,"pinned":true}
POST /api/news/remove
content-type: application/json
x-obbystreams-token: <token>

{"id":"news-1781990000000"}

The current production public source inventory and nested-playlist notes live in public_srcs.md.

Stream Actions

Start:

POST /api/stream/start
content-type: application/json
x-obbystreams-token: <token>

{"kill_existing":true}

Start with one-off links:

{"kill_existing":true,"links":["https://example.com/live.m3u8"]}

Stop:

POST /api/stream/stop
x-obbystreams-token: <token>

Restart:

POST /api/stream/restart
content-type: application/json
x-obbystreams-token: <token>

{"kill_existing":true}

ArangoDB

GET /api/arango
x-obbystreams-token: <token>

Returns whether the configured ArangoDB endpoint is reachable from the app.

NVIDIA SMI

GET /api/nvidia-smi
x-obbystreams-token: <token>

Returns cached NVIDIA GPU telemetry. The collector runs at most once every five seconds and reports:

  • GPU name, UUID, driver, pstate, clocks, temperature, utilization, memory, and power.
  • NVENC encoder session count, average FPS, and average latency when supported.
  • Compute and pmon process rows.
  • Whether an ffmpeg/NVENC process appears active.
  • Command summaries for failed optional nvidia-smi queries.

If nvidia-smi is unavailable, the route still returns ok: true with available: false so the frontend can display degraded GPU telemetry without breaking the dashboard.

Stream Control (persistent)

POST /api/stream/start
POST /api/stream/stop
POST /api/stream/restart

stop persists stream.operator_stopped: true (kills ffmpeg AND idles both scrapers until an explicit start); start and restart clear it. The flag survives supervisor ticks and full restarts. stop responds with {"ok": true, "stopped": <bool>, "operator_stopped": true}. Current state is exposed at runtime.operator_stopped in GET /api/status.

Blacklist

GET  /api/blacklist
POST /api/blacklist          { "url"?, "id"?, "label"?, "channel"?, "reason"? }
POST /api/blacklist/remove   { "url"? , "id"? }

POST /api/blacklist persists a block (matching by URL, URL-without-query, id, channel, or label) and immediately strips matching entries from public_sources and stream.sources. Blocked sources are filtered from every scraper cycle and every viewer-facing list. add requires at least one of url/id/label/channel (400 otherwise). See Persistent Stop & Source Blacklist.

HLS Proxy

GET /hls/ufc.m3u8
GET /hls/ufc123.ts

The proxy:

  1. Serves files from stream.output_dir when present.
  2. Rewrites relative playlist segment paths to /hls/*.
  3. Falls back to stream.public_hls_url.
  4. Falls back to the hardcoded fight stream URL used by the existing deployment.

The HLS proxy is intentionally unauthenticated so the browser player can load media once the dashboard shell is open.