Integration Guide

This page provides examples and implementation notes for integrating REFEREE into your web app or streaming workflow.

npm Client Package

The official @cmacrowther/referee-client package bundles TypeScript types, a RefereeClientclass with auto-auth and managed heartbeating, and a plug-and-play React hook — so you don't have to wire up the lifecycle yourself.

npm install @cmacrowther/referee-client

Usage

import { RefereeClient } from '@cmacrowther/referee-client';
import { useReferee } from '@cmacrowther/referee-client/react';

// ─── Vanilla JS / TypeScript ─────────────────────────────────────────────────
const referee = new RefereeClient({ appName: 'MyApp' });

// Token is requested automatically on first authenticated call.
// In desktop mode this triggers a consent dialog; pre-approve
// origins on headless via REFEREE_ALLOWED_ORIGINS env var.
const status = await referee.getStatus();
if (!status.gpuReady) return; // no compatible GPU

// Start session (blocking until HLS output is ready)
const { session, dispose } = await referee.startManagedSession({
  url: 'https://example.com/live/stream.m3u8',
  streamTitle: 'Friday Night Stream',
});

// session.url is the absolute HLS URL — load it directly in your player
player.src = session.url;

// dispose() stops heartbeat + session and cleans up event listeners
window.addEventListener('pagehide', dispose);

// ─── React ───────────────────────────────────────────────────────────────────
function VideoPlayer({ sourceUrl }) {
  const { start, stop, status, playbackUrl } = useReferee({ appName: 'MyApp' });

  useEffect(() => {
    start({ url: sourceUrl, streamTitle: 'Live Stream' });
    return () => stop();
  }, [sourceUrl]);

  if (status === 'unavailable') return <NativePlayer src={sourceUrl} />;
  if (playbackUrl)              return <HlsPlayer src={playbackUrl} />;
  return <LoadingSpinner />;
}

Quick Start

These examples show how to integrate REFEREE into your web app or streaming workflow. The API is designed to be simple and flexible, so you can implement it in any language or framework that can make HTTP requests . Each example is standalone — start with the one that best matches your stack, or mix and match patterns as needed. For a quick start, see the graceful degradation example, which shows how to add REFEREE support without disrupting existing playback for users without compatible GPUs.

const REFEREE_BASE = 'http://localhost:14002';

// ─── Option A: Request a token automatically (recommended) ───────────────────
//
// POST /v1/auth/request triggers the REFEREE desktop consent dialog.
// If the origin is already approved (or pre-set via REFEREE_ALLOWED_ORIGINS),
// the token is returned immediately without user interaction.
// The returned token is persistent — cache it in sessionStorage or memory.

async function requestToken(appName) {
  const res = await fetch(`${REFEREE_BASE}/v1/auth/request`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ appName }),
  });

  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    // 403 HEADLESS_MODE — origin not pre-approved on a headless server.
    // Add it via REFEREE_ALLOWED_ORIGINS env var or POST /v1/origins.
    throw new Error(err.error ?? `Auth failed: ${res.status}`);
  }

  const { token, persistent } = await res.json();
  console.log(`Token ${persistent ? '(saved)' : '(one-time)'}: ${token}`);
  return token;
}

// ─── Option B: Use a known token directly ────────────────────────────────────
//
// If you control the server, set REFEREE_API_TOKEN on startup and
// hardcode or inject the token — no auth request needed.
// Retrieve it from:
//   • Desktop app: Settings → API Token
//   • Headless:    REFEREE_API_TOKEN env var → token is printed on startup
//   • Headless:    POST /v1/auth/rotate-token (generates a new one)

const REFEREE_TOKEN = process.env.REFEREE_API_TOKEN ?? '<your-referee-token>';

// ─── Usage ───────────────────────────────────────────────────────────────────
let token = null;

async function getOrRequestToken() {
  if (token) return token;
  token = await requestToken('MyApp');
  return token;
}

// All authenticated calls pass the token as X-Referee-Token
async function startSession(sourceUrl) {
  const t = await getOrRequestToken();
  const res = await fetch(`${REFEREE_BASE}/v1/stream/start`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Referee-Token': t,
    },
    body: JSON.stringify({ url: sourceUrl, appName: 'MyApp' }),
  });
  return res.json();
}

Session Lifecycle

1. GET /v1/status — Verify REFEREE is healthy and GPU is ready

2. POST /v1/stream/start — Blocking call (up to ~3 min) — waits until upscaled HLS output is ready

3. Load HLS URL in player — Begin playback (e.g., hls.js, Video.js)

4. POST /v1/stream/heartbeat/{sessionId} — Send every 10 seconds while playing

5. POST /v1/stream/stop — Stop session when playback ends

Heartbeat Contract

Recommended interval

10 seconds

Server timeout

15 seconds

Missed heartbeats tolerated

~0–1 consecutive

Orphan grace period

5 minutes

Before first heartbeat only. After heartbeats start, server timeout (15 s) applies directly.

CORS & Reverse Proxy

REFEREE sets Access-Control-Allow-Origin: * on GET /v1/status, POST /v1/auth/request, and HLS segment responses, so browsers can access those without issue. All other endpoints (stream start/stop/heartbeat, origins management) only allow cross-origin requests from origins in the approved-origins list — unapproved cross-origin XHR to those endpoints will be blocked by the browser.

Mixed-content block: If your web app is served over https://, browsers will block requests to http://localhost:14002. During development, serve your app over HTTP, or use the nginx reverse proxy below.

nginx reverse proxy (example)

Place this inside your server block to expose REFEREE under your own HTTPS domain.

location /referee/ {
    proxy_pass         http://localhost:14002/;
    proxy_http_version 1.1;
    proxy_set_header   Host $host;
    proxy_set_header   X-Real-IP $remote_addr;

    # Allow the browser to consume the HLS stream cross-origin
    add_header Access-Control-Allow-Origin  "*" always;
    add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Content-Type, X-Referee-Token" always;

    # Required for streaming TS segments without buffering
    proxy_buffering    off;
    proxy_read_timeout 300s;
}

Then set REFEREE_BASE = 'https://yourdomain.com/referee' in your integration code.

API Reference

All endpoints accept and return JSON. Base URL: http://localhost:14002

Versioning

All routes are prefixed with /v1/. Future breaking changes will increment the version number, so existing integrations continue to work.

Authentication

Mutating endpoints (POST /v1/stream/start, /heartbeat, /stop, and the /v1/auth/rotate-token and /v1/origins management endpoints) require an X-Referee-Token header.

Desktop app: retrieve the token from the REFEREE settings panel or call POST /v1/auth/request from a browser page — REFEREE shows a consent dialog and returns the token on approval.

Headless server: set the REFEREE_API_TOKEN environment variable (≥ 32 chars) before starting the server to use a known token. Pre-approve origins with REFEREE_ALLOWED_ORIGINS (comma-separated) so browser clients can obtain the token via POST /v1/auth/request without a UI. Unknown origins receive 403 HEADLESS_MODE.

Unauthenticated endpoints: GET /v1/status, GET /v1/ping, GET /v1/tmp/* (HLS segments), and POST /v1/auth/request. Note: GET /v1/origins is token-protected.

Supported Source Formats

Pass any of the following as the url field in POST /v1/stream/start.

FormatExampleNotes
HLS (.m3u8)https://…/stream.m3u8Recommended. Live and VOD HLS streams. Supports auth headers.
DASH (.mpd)https://…/manifest.mpdBest-effort via FFmpeg demuxer — no first-class manifest handling. Behaviour may vary.
Direct video URLhttps://…/video.mp4MP4, MKV, and other FFmpeg-compatible container formats.

Heads up: HLS segments must end in .ts / .m4s

FFmpeg 7.0+ enforces an allowed_segment_extensions allowlist on HLS playlists and rejects segments whose URL path does not end in one of ts, m4s, etc. Query parameters are not inspected, so a stream-proxy URL like:

https://proxy.example.com/api/stream-proxy?url=…/segment.ts&sig=…

will fail metadata probing with URL … is not in allowed_segment_extensions even though the underlying segment is a valid MPEG-TS file.

Fixes (pick one):

  • Reshape the proxy URL so the path ends in .ts / .m4s, e.g. /api/stream-proxy/segment.ts?url=…. This is the most portable fix and also keeps third-party HLS players happy.
  • Rewrite the playlist server-side before returning it, so segment URIs already include the expected extension on the path.
  • Pass the original .m3u8 URL to POST /v1/stream/start with any required headers (referer, origin, cookies) and let REFEREE fetch segments directly instead of routing through an extension-stripping proxy.

Error Codes

All error responses include a machine-readable code field alongside the human-readable error message:

{
  "error": "Session not found",
  "code": "SESSION_NOT_FOUND"
}
StatusMeaningAction
400 INVALID_REQUESTMissing or invalid request fieldCheck your request body
400 MISSING_ORIGINNo Origin header on auth requestBrowsers set this automatically; non-browser callers must add it manually
400 INVALID_ORIGINOrigin is not a valid http/https URLEnsure the Origin header starts with http:// or https://
401 UNAUTHORIZEDMissing or invalid API tokenInclude a valid X-Referee-Token header
403 NO_APP_HANDLEDesktop app handle is unavailableRare internal state; retry or restart the desktop app
403 CONSENT_DENIEDUser clicked Deny on the consent dialogPrompt the user to allow access in REFEREE and retry
403 HEADLESS_MODEOrigin not pre-approved on a headless serverAdd origin via POST /v1/origins or REFEREE_ALLOWED_ORIGINS env var
404 ORIGIN_NOT_FOUNDOrigin not in approved list (DELETE)Check the origin spelling and encoding
404 SESSION_NOT_FOUNDSession not foundSession was already cleaned up — stop heartbeat
502 PIPELINE_EXITEDPipeline exited earlyEncoder crashed before producing output — check source URL is reachable
503 NO_ENCODERNo compatible GPUREFEREE cannot upscale without a compatible GPU
504 PIPELINE_TIMEOUTPipeline timed outGPU may be busy or source stream unreachable
408 CONSENT_TIMEOUTUser did not respond to the consent dialog within 180 sRetry the auth request — REFEREE will show the dialog again
429 RATE_LIMITEDToo many requests (5/min for auth, 3/min for stream-start)Wait for the duration in the Retry-After: 60 response header before retrying
400 INVALID_URLStream URL is not a valid http/https URLOnly http:// and https:// URLs are accepted as the url field
400 SSRF_BLOCKEDStream URL resolves to a loopback, private, or link-local addressUse a publicly reachable URL; internal addresses are blocked for security
400 INVALID_HEADERSThe headers object contains a forbidden header name or a CRLF character in a valueRemove hop-by-hop and forwarding headers (e.g. Host, X-Forwarded-For, Transfer-Encoding)

FAQ

Common questions from developers integrating REFEREE.