Skip to content

Configuration Guide

Complete reference for the bilbycast-edge JSON configuration file. This guide covers every field, validation rule, and common configuration patterns.



bilbycast-edge reads its configuration from two JSON files:

  • config.json — Operational configuration (specified by --config, default: ./config.json). Contains server settings, flow definitions (including user-configured parameters like SRT passphrases, RTSP credentials, RTMP stream keys, bearer tokens, HLS auth tokens), and tunnel routing.
  • secrets.json — Infrastructure credentials (auto-derived: same directory as config.json). Contains manager auth secrets, tunnel encryption keys, API auth config (JWT secret, client credentials), TLS cert/key paths. Written with 0600 permissions on Unix.

If neither file exists at startup, an empty default configuration is used. Both files are loaded and merged into a single in-memory config, then validated at startup. Changes made through the API or manager commands are automatically persisted — flow configs and operational fields to config.json, infrastructure secrets to secrets.json — using atomic writes (write to temp file, then rename).

Migration: If upgrading from a version that used a single config.json with secrets, the node automatically splits them on first startup.


{
"version": 1,
"device_name": "Studio-A Encoder",
"setup_enabled": true,
"server": {
"listen_addr": "0.0.0.0",
"listen_port": 8080,
"tls": {
"cert_path": "/etc/bilbycast/cert.pem",
"key_path": "/etc/bilbycast/key.pem"
},
"auth": {
"enabled": true,
"jwt_secret": "a-cryptographically-random-string-of-at-least-32-characters",
"token_lifetime_secs": 3600,
"public_metrics": true,
"clients": [
{
"client_id": "admin",
"client_secret": "admin-secret-here",
"role": "admin"
},
{
"client_id": "grafana",
"client_secret": "grafana-secret-here",
"role": "monitor"
}
]
}
},
"monitor": {
"listen_addr": "0.0.0.0",
"listen_port": 9090
},
"flows": [
{
"id": "main-feed",
"name": "Main Program Feed",
"enabled": true,
"input": {
"type": "rtp",
"bind_addr": "239.1.1.1:5000",
"interface_addr": "192.168.1.100",
"fec_decode": {
"columns": 10,
"rows": 10
},
"allowed_sources": ["10.0.0.1", "10.0.0.2"],
"allowed_payload_types": [33],
"max_bitrate_mbps": 100.0,
"tr07_mode": true
},
"outputs": [
{
"type": "rtp",
"id": "rtp-local",
"name": "Local Playout",
"dest_addr": "192.168.1.50:5004",
"interface_addr": "192.168.1.100",
"fec_encode": {
"columns": 10,
"rows": 10
},
"dscp": 46
},
{
"type": "srt",
"id": "srt-remote",
"name": "Remote Site via SRT",
"mode": "caller",
"local_addr": "0.0.0.0:0",
"remote_addr": "203.0.113.10:9000",
"latency_ms": 500,
"passphrase": "my-encryption-passphrase",
"aes_key_len": 32
},
{
"type": "rtmp",
"id": "twitch-out",
"name": "Twitch Stream",
"dest_url": "rtmp://live.twitch.tv/app",
"stream_key": "live_123456789_abcdefghijklmnop",
"reconnect_delay_secs": 5,
"max_reconnect_attempts": 10
}
]
}
]
}

FieldTypeRequiredDefaultDescription
versionintegerYes-Schema version. Currently must be 1.
node_idstringNoAuto-generatedPersistent UUID v4 identifying this edge node. Auto-generated on first startup and saved to config. Used as the NMOS IS-04 Node ID.
device_namestringNonullOptional human-readable label for this edge node (e.g. “Studio-A Encoder”). Max 256 characters.
setup_enabledbooleanNotrueWhen true, the browser-based setup wizard is accessible at /setup. Set to false to disable after provisioning.
serverobjectYes-API server configuration.
monitorobjectNonullWeb monitoring dashboard configuration.
managerobjectNonullManager WebSocket connection configuration. See Manager Configuration.
flowsarrayNo[]List of flow configurations. See Flow Configuration.
tunnelsarrayNo[]List of IP tunnel configurations. See Tunnel Configuration.

The server object controls the API server listener.

{
"server": {
"listen_addr": "0.0.0.0",
"listen_port": 8080,
"tls": { ... },
"auth": { ... }
}
}
FieldTypeRequiredDefaultDescription
listen_addrstringYes"0.0.0.0"IP address to bind the API server to. Use "0.0.0.0" for all interfaces or a specific IP.
listen_portintegerYes8080TCP port for the API server.
tlsobjectNonullTLS configuration for HTTPS (tls feature enabled by default).
authobjectNonullOAuth 2.0 / JWT authentication configuration. When absent or enabled: false, all endpoints are open.

Optional sub-object of server. The tls feature is enabled by default.

{
"tls": {
"cert_path": "/etc/bilbycast/cert.pem",
"key_path": "/etc/bilbycast/key.pem"
}
}
FieldTypeRequiredDescription
cert_pathstringYesPath to PEM-encoded TLS certificate file (or fullchain). Cannot be empty.
key_pathstringYesPath to PEM-encoded TLS private key file. Cannot be empty.

If TLS is configured but the binary was built without the tls feature, a warning is logged and the server starts without TLS.


Optional sub-object of server. See the Security Guide for detailed usage.

{
"auth": {
"enabled": true,
"jwt_secret": "at-least-32-characters-of-random-data",
"token_lifetime_secs": 3600,
"public_metrics": true,
"clients": [
{
"client_id": "admin",
"client_secret": "strong-secret",
"role": "admin"
}
]
}
}
FieldTypeRequiredDefaultDescription
enabledbooleanYes-Master switch. When false, all endpoints are open.
jwt_secretstringYes (if enabled)-HMAC-SHA256 signing secret. Must be >= 32 characters.
token_lifetime_secsintegerNo3600JWT token lifetime in seconds.
public_metricsbooleanNotrueWhether /metrics and /health are accessible without auth.
clientsarrayYes (if enabled)-Registered OAuth clients. At least one required.

Client fields:

FieldTypeRequiredDescription
client_idstringYesUnique client identifier. Cannot be empty.
client_secretstringYesClient authentication secret. Cannot be empty.
rolestringYesMust be "admin" or "monitor".

Optional top-level object. When present, bilbycast-edge starts a second HTTP server serving a self-contained HTML monitoring dashboard.

{
"monitor": {
"listen_addr": "0.0.0.0",
"listen_port": 9090
}
}
FieldTypeRequiredDescription
listen_addrstringYesIP address for the dashboard server.
listen_portintegerYesTCP port for the dashboard. Must differ from server.listen_port if the same listen_addr is used.

Validation: The monitor address must differ from the API server address (same IP + same port is rejected).


Optional connection to a bilbycast-manager instance for centralized monitoring and remote control. All communication uses an outbound WebSocket connection from the edge to the manager — no inbound connections are required, making this work behind NAT and firewalls.

{
"manager": {
"enabled": true,
"url": "wss://manager-host:8443/ws/node",
"accept_self_signed_cert": false,
"cert_fingerprint": "ab:cd:ef:01:23:45:67:89:..."
}
}
FieldTypeRequiredDefaultDescription
enabledbooleanNofalseEnable the manager connection.
urlstringYes (if enabled)-Manager WebSocket URL. Must use wss:// (TLS required). Example: "wss://manager-host:8443/ws/node". Max 2048 characters.
accept_self_signed_certbooleanNofalseAccept self-signed TLS certificates from the manager. Dev/testing only — disables all TLS validation. Requires BILBYCAST_ALLOW_INSECURE=1 environment variable as a safety guard.
cert_fingerprintstringNonullSHA-256 fingerprint of the manager’s TLS certificate for certificate pinning. Format: hex with colons, e.g. "ab:cd:ef:01:23:...". When set, connections to servers presenting a different certificate are rejected, even if the certificate is CA-signed. Protects against compromised CAs. The server’s fingerprint is logged on first connection.
registration_tokenstringNonullOne-time registration token from the manager. Used on first connection only. After successful registration, the token is cleared and replaced by node_id + node_secret. Stored in secrets.json.
node_idstringNonullPersistent node ID assigned by the manager during registration. Saved automatically.
node_secretstringNonullPersistent node secret assigned by the manager during registration. Stored in secrets.json (encrypted at rest).
  1. Create a node in the manager UI — you receive a one-time registration token.
  2. Provide the token via the setup wizard (http://<edge-ip>:8080/setup) or in secrets.json.
  3. Start the edge. It connects to the manager, sends the token, and receives node_id + node_secret.
  4. Credentials are saved automatically: node_id to config.json, node_secret to secrets.json.
  5. The registration token is cleared. Future connections use node_id + node_secret.
  6. If the connection drops, the edge auto-reconnects with exponential backoff (1s to 60s).
  • url must start with wss:// (plaintext ws:// is rejected).
  • url max 2048 characters.
  • registration_token max 4096 characters.
  • accept_self_signed_cert: true is rejected unless BILBYCAST_ALLOW_INSECURE=1 is set.

IP tunnels create encrypted point-to-point links between edge nodes, either through a bilbycast-relay server (for NAT traversal) or directly via QUIC (when one edge has a public IP).

Both edges connect outbound to a bilbycast-relay server. The relay pairs them by tunnel UUID and forwards traffic. End-to-end encryption ensures the relay cannot read payloads.

{
"tunnels": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Stadium to Studio",
"protocol": "udp",
"mode": "relay",
"direction": "egress",
"local_addr": "0.0.0.0:9000",
"relay_addr": "relay.example.com:4433",
"tunnel_encryption_key": "0123456789abcdef...",
"tunnel_bind_secret": "fedcba9876543210..."
}
]
}

One edge has a public IP. Direct QUIC connection between edges — no relay needed.

{
"tunnels": [
{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"name": "Direct Link",
"protocol": "tcp",
"mode": "direct",
"direction": "ingress",
"local_addr": "127.0.0.1:9000",
"direct_listen_addr": "0.0.0.0:4433",
"tunnel_psk": "abcdef0123456789..."
}
]
}
FieldTypeRequiredDefaultDescription
idstringYes-Unique tunnel identifier. Must be a valid UUID. Both edges in a tunnel pair must use the same ID.
namestringYes-Human-readable name.
enabledbooleanNotrueWhether the tunnel is active.
protocolstringYes-"tcp" (reliable, ordered — QUIC streams) or "udp" (unreliable — QUIC datagrams, best for SRT and media).
modestringYes-"relay" (via relay server) or "direct" (QUIC peer-to-peer).
directionstringYes-"ingress" (receives tunnel traffic, forwards to local_addr) or "egress" (listens on local_addr, sends into tunnel).
local_addrstringYes-For egress: listen address for local traffic to tunnel (e.g. "0.0.0.0:9000"). For ingress: forward destination for received traffic (e.g. "127.0.0.1:9000").
relay_addrstringRelay modenullRelay server QUIC address (e.g. "relay.example.com:4433"). Required for relay mode.
tunnel_encryption_keystringRelay modenullEnd-to-end ChaCha20-Poly1305 encryption key. Hex-encoded, exactly 64 chars (32 bytes). Required for relay mode. Both edges must share the same key. Stored in secrets.json.
tunnel_bind_secretstringNonullHMAC-SHA256 bind authentication secret. Hex-encoded, exactly 64 chars. Proves authorization to bind on the relay. Stored in secrets.json.
peer_addrstringDirect egressnullRemote peer QUIC address (e.g. "203.0.113.50:4433"). Required for direct mode, egress direction.
direct_listen_addrstringDirect ingressnullQUIC listen address (e.g. "0.0.0.0:4433"). Required for direct mode, ingress direction.
tunnel_pskstringNonullPre-shared key for direct mode authentication. Hex-encoded, 64 chars. Both edges must share the same PSK. Stored in secrets.json.
tls_cert_pemstringNoAuto-generatedTLS certificate PEM for direct mode listener. Auto-generated if absent. Stored in secrets.json.
tls_key_pemstringNoAuto-generatedTLS private key PEM for direct mode listener. Stored in secrets.json.
  • id must be a valid UUID.
  • relay_addr required when mode is "relay".
  • tunnel_encryption_key required for relay mode; must be exactly 64 hex characters.
  • tunnel_bind_secret must be exactly 64 hex characters if present.
  • peer_addr required for direct mode egress.
  • direct_listen_addr required for direct mode ingress.
  • tunnel_psk must be exactly 64 hex characters if present.
  • All address fields must be valid socket addresses.

Each flow defines one input source fanning out to one or more output destinations.

{
"id": "main-feed",
"name": "Main Program Feed",
"enabled": true,
"input": { ... },
"outputs": [ ... ]
}
FieldTypeRequiredDefaultDescription
idstringYes-Unique identifier. Cannot be empty. Must be unique across all flows.
namestringYes-Human-readable display name. Cannot be empty.
enabledbooleanNotrueWhether to auto-start this flow on startup or creation.
inputobjectYes-Input source configuration (RTP, UDP, SRT, RTMP, RTSP, WebRTC, or WHEP).
outputsarrayYes-Output destination configurations. Can be empty. Output IDs must be unique within the flow.

The input object uses a type discriminator field to determine which input variant is used: rtp, udp, srt, rtmp, rtsp, webrtc, or whep.

Receives RTP-wrapped MPEG-TS packets (SMPTE ST 2022-2). Requires valid RTP v2 headers. Supports unicast, multicast, IPv4, and IPv6. For raw TS without RTP headers, use the UDP input type.

{
"type": "rtp",
"bind_addr": "239.1.1.1:5000",
"interface_addr": "192.168.1.100",
"fec_decode": {
"columns": 10,
"rows": 10
},
"allowed_sources": ["10.0.0.1"],
"allowed_payload_types": [33],
"max_bitrate_mbps": 100.0,
"tr07_mode": true
}
FieldTypeRequiredDefaultDescription
typestringYes-Must be "rtp".
bind_addrstringYes-Local socket address to bind (ip:port). For multicast, use the group address (e.g., "239.1.1.1:5000"). For unicast, use "0.0.0.0:5000". IPv6: "[::]:5000" or "[ff7e::1]:5000".
interface_addrstringNonullNetwork interface IP for multicast group join. Required for multicast on multi-homed hosts. Must be the same address family as bind_addr.
fec_decodeobjectNonullSMPTE 2022-1 FEC decode parameters. See FEC Configuration.
tr07_modebooleanNonullEnable VSF TR-07 mode to detect and report JPEG XS streams in the transport stream.
allowed_sourcesarray of stringsNonullSource IP allow-list (RP 2129 C5). Only RTP packets from these source IPs are accepted. Each entry must be a valid IP address. When null, all sources are allowed.
allowed_payload_typesarray of integersNonullRTP payload type allow-list (RP 2129 U4). Only packets with these PT values (0-127) are accepted. When null, all payload types are allowed.
max_bitrate_mbpsfloatNonullMaximum ingress bitrate in megabits per second (RP 2129 C7). Excess packets are dropped. Must be positive. When null, no rate limiting is applied.

Validation rules:

  • bind_addr must be a valid ip:port socket address.
  • interface_addr must be a valid IP address (no port) in the same address family as bind_addr.
  • allowed_payload_types values must be 0-127.
  • max_bitrate_mbps must be positive.

Receives raw UDP datagrams without requiring RTP headers. Suitable for raw MPEG-TS over UDP from OBS, ffmpeg (-f mpegts udp://), srt-live-transmit, VLC, or any source that sends plain TS.

{
"type": "udp",
"bind_addr": "0.0.0.0:5000",
"interface_addr": "192.168.1.100"
}
FieldTypeRequiredDefaultDescription
typestringYes-Must be "udp".
bind_addrstringYes-Local socket address to bind (ip:port). For multicast, use the group address.
interface_addrstringNonullNetwork interface IP for multicast group join. Must be the same address family as bind_addr.

Validation rules:

  • bind_addr must be a valid ip:port socket address.
  • interface_addr must be a valid IP address in the same address family as bind_addr.

Receives RTP encapsulated in SRT. Supports caller, listener, and rendezvous modes with optional encryption and SMPTE 2022-7 redundancy.

{
"type": "srt",
"mode": "listener",
"local_addr": "0.0.0.0:9000",
"remote_addr": null,
"latency_ms": 500,
"passphrase": "my-encryption-key",
"aes_key_len": 32,
"crypto_mode": "aes-gcm",
"redundancy": {
"mode": "listener",
"local_addr": "0.0.0.0:9001",
"latency_ms": 500,
"passphrase": "my-encryption-key",
"aes_key_len": 32,
"crypto_mode": "aes-gcm"
}
}
FieldTypeRequiredDefaultDescription
typestringYes-Must be "srt".
modestringYes-SRT connection mode: "caller", "listener", or "rendezvous". See SRT Connection Modes.
local_addrstringYes-Local socket address to bind (ip:port).
remote_addrstringConditionalnullRemote address to connect to. Required for caller and rendezvous modes.
latency_msintegerNo120SRT receive latency buffer in milliseconds. Higher values provide more resilience to network jitter at the cost of increased delay.
passphrasestringNonullAES encryption passphrase. Must be 10-79 characters. When null, encryption is disabled.
aes_key_lenintegerNo16AES key length in bytes: 16 (AES-128), 24 (AES-192), or 32 (AES-256). Only meaningful if passphrase is set.
crypto_modestringNonullCipher mode: "aes-ctr" (default) or "aes-gcm" (authenticated encryption). AES-GCM requires libsrt >= 1.5.2 on the peer and only supports AES-128/256 (not AES-192).
redundancyobjectNonullSMPTE 2022-7 redundancy configuration for a second SRT leg. See SRT Redundancy.

Validation rules:

  • local_addr must be a valid socket address.
  • remote_addr is required for caller and rendezvous modes and must be a valid socket address.
  • passphrase must be 10-79 characters.
  • aes_key_len must be 16, 24, or 32.
  • crypto_mode must be "aes-ctr" or "aes-gcm". AES-GCM with aes_key_len 24 is rejected.

Accepts incoming RTMP publish connections from OBS, ffmpeg, Wirecast, etc.

{
"type": "rtmp",
"listen_addr": "0.0.0.0:1935",
"app": "live",
"stream_key": "my_secret_key"
}

Pulls H.264 or H.265/HEVC video and AAC audio from RTSP sources (IP cameras, media servers). Uses the retina pure-Rust RTSP client with automatic reconnection. Produces MPEG-TS with proper PAT/PMT program tables. Audio-only streams are supported (PAT/PMT are emitted even without video).

{
"type": "rtsp",
"rtsp_url": "rtsp://camera.local:554/stream1",
"username": "admin",
"password": "secret",
"transport": "tcp"
}
FieldTypeRequiredDefaultDescription
typestringYes-Must be "rtsp".
rtsp_urlstringYes-RTSP source URL. Must start with rtsp:// or rtsps://.
usernamestringNonullRTSP authentication username (Digest or Basic).
passwordstringNonullRTSP authentication password.
transportstringNo"tcp""tcp" (interleaved, reliable) or "udp" (lower latency).
timeout_secsintegerNo10Connection timeout in seconds.
reconnect_delay_secsintegerNo5Delay between reconnection attempts on failure.

Accepts WebRTC contributions from publishers (OBS, browsers) via the WHIP protocol (RFC 9725). The webrtc feature is enabled by default.

{
"type": "webrtc",
"bearer_token": "my-auth-token"
}

Publishers POST an SDP offer to /api/v1/flows/{flow_id}/whip and receive an SDP answer. The Bearer token (if configured) must be included in the Authorization header.

FieldTypeRequiredDefaultDescription
typestringYes-Must be "webrtc".
bearer_tokenstringNonullRequired from WHIP publishers for authentication.
video_onlybooleanNofalseIgnore audio tracks from publisher.
public_ipstringNonullPublic IP to advertise in ICE candidates (for NAT traversal).
stun_serverstringNonullSTUN server URL for ICE candidate gathering.

Pulls media from an external WHEP server. The edge acts as a WHEP client. The webrtc feature is enabled by default.

{
"type": "whep",
"whep_url": "https://server.example.com/whep/stream",
"bearer_token": "optional-token"
}
FieldTypeRequiredDefaultDescription
typestringYes-Must be "whep".
whep_urlstringYes-WHEP endpoint URL to pull from.
bearer_tokenstringNonullBearer token for WHEP authentication.
video_onlybooleanNofalseReceive only video (ignore audio).

Each output has a type discriminator. All outputs share id and name fields.

Sends RTP-wrapped MPEG-TS packets to a unicast or multicast destination. Supports SMPTE 2022-1 FEC encoding.

{
"type": "rtp",
"id": "rtp-out-1",
"name": "Local Playout",
"dest_addr": "192.168.1.50:5004",
"bind_addr": "192.168.1.100:0",
"interface_addr": "192.168.1.100",
"fec_encode": {
"columns": 10,
"rows": 10
},
"dscp": 46
}
FieldTypeRequiredDefaultDescription
typestringYes-Must be "rtp".
idstringYes-Unique output ID within the flow. Cannot be empty.
namestringYes-Human-readable display name.
dest_addrstringYes-Destination socket address (ip:port). For multicast, use the group address (e.g., "239.1.2.1:5004"). IPv6: "[::1]:5004".
bind_addrstringNo"0.0.0.0:0"Source bind address. Use to control the source IP/port of outgoing packets. Must be same address family as dest_addr.
interface_addrstringNonullNetwork interface IP for multicast send. Must be same address family as dest_addr.
fec_encodeobjectNonullSMPTE 2022-1 FEC encode parameters. See FEC Configuration.
dscpintegerNo46DSCP value for QoS marking (RP 2129 C10). Range 0-63. Default 46 = Expedited Forwarding (RFC 4594).

Validation rules:

  • id cannot be empty.
  • dest_addr, bind_addr, and interface_addr must all use the same address family.
  • dscp must be 0-63.

Sends raw MPEG-TS over UDP without RTP headers. Datagrams are TS-aligned (7×188 = 1316 bytes). If the input is RTP-wrapped, RTP headers are automatically stripped. Compatible with ffplay, VLC, and standard IP/TS multicast receivers.

{
"type": "udp",
"id": "udp-out-1",
"name": "Local Playout (raw TS)",
"dest_addr": "192.168.1.50:5004",
"dscp": 46
}
FieldTypeRequiredDefaultDescription
typestringYes-Must be "udp".
idstringYes-Unique output ID within the flow.
namestringYes-Human-readable display name.
dest_addrstringYes-Destination socket address (ip:port). For multicast, use the group address.
bind_addrstringNo"0.0.0.0:0"Source bind address. Must be same address family as dest_addr.
interface_addrstringNonullNetwork interface IP for multicast send.
dscpintegerNo46DSCP value for QoS marking. Range 0-63.

Validation rules:

  • id cannot be empty.
  • dest_addr must be a valid socket address.
  • dscp must be 0-63.

Sends RTP encapsulated in SRT.

{
"type": "srt",
"id": "srt-out-1",
"name": "Remote Site",
"mode": "caller",
"local_addr": "0.0.0.0:0",
"remote_addr": "203.0.113.10:9000",
"latency_ms": 500,
"passphrase": "encryption-key-here",
"aes_key_len": 32,
"redundancy": {
"mode": "caller",
"local_addr": "0.0.0.0:0",
"remote_addr": "203.0.113.11:9000",
"latency_ms": 500,
"passphrase": "encryption-key-here",
"aes_key_len": 32
}
}
FieldTypeRequiredDefaultDescription
typestringYes-Must be "srt".
idstringYes-Unique output ID within the flow. Cannot be empty.
namestringYes-Human-readable display name.
modestringYes-SRT connection mode: "caller", "listener", or "rendezvous".
local_addrstringYes-Local socket address to bind. Use "0.0.0.0:0" for caller mode (ephemeral port).
remote_addrstringConditionalnullRemote address. Required for caller and rendezvous.
latency_msintegerNo120SRT send latency in milliseconds.
passphrasestringNonullAES encryption passphrase (10-79 characters).
aes_key_lenintegerNo16AES key length: 16, 24, or 32.
crypto_modestringNonullCipher mode: "aes-ctr" (default) or "aes-gcm".
redundancyobjectNonullSMPTE 2022-7 redundancy for a second SRT output leg.

Publishes to an RTMP/RTMPS server (e.g., Twitch, YouTube Live, Facebook Live). Demuxes H.264 and AAC from MPEG-2 TS and muxes into FLV.

{
"type": "rtmp",
"id": "twitch",
"name": "Twitch Stream",
"dest_url": "rtmp://live.twitch.tv/app",
"stream_key": "live_123456789_abcdefghijklmnop",
"reconnect_delay_secs": 5,
"max_reconnect_attempts": 10
}
FieldTypeRequiredDefaultDescription
typestringYes-Must be "rtmp".
idstringYes-Unique output ID. Cannot be empty.
namestringYes-Human-readable display name.
dest_urlstringYes-RTMP server URL. Must start with rtmp:// or rtmps://. RTMPS requires the tls feature (enabled by default).
stream_keystringYes-Stream key for authentication with the RTMP server. Cannot be empty.
reconnect_delay_secsintegerNo5Seconds to wait before reconnecting after a failure. Must be > 0.
max_reconnect_attemptsintegerNonull (unlimited)Maximum reconnection attempts. When null, reconnects indefinitely.

Limitations:

  • Output only. RTMP input is not supported.
  • Only H.264 video and AAC audio are supported (no HEVC/VP9).

Segments MPEG-2 TS data and uploads via HTTP for HLS ingest (e.g., YouTube HLS).

{
"type": "hls",
"id": "youtube-hls",
"name": "YouTube HLS",
"ingest_url": "https://a.upload.youtube.com/http_upload_hls?cid=xxxx&copy=0&file=index.m3u8",
"segment_duration_secs": 2.0,
"auth_token": "ya29.a0ARrdaM...",
"max_segments": 5
}
FieldTypeRequiredDefaultDescription
typestringYes-Must be "hls".
idstringYes-Unique output ID. Cannot be empty.
namestringYes-Human-readable display name.
ingest_urlstringYes-HLS ingest base URL. Must start with http:// or https://.
segment_duration_secsfloatNo2.0Target segment duration in seconds. Range: 0.5-10.0.
auth_tokenstringNonullBearer token sent with each HTTP upload request.
max_segmentsintegerNo5Maximum segments in the rolling playlist. Range: 1-30.

Limitations:

  • Output only. Segment-based transport inherently adds 1-4 seconds of latency.

Supports two modes: WHIP client (push to external endpoint) and WHEP server (serve viewers). The webrtc feature is enabled by default.

WHIP Client mode — push to an external WHIP endpoint:

{
"type": "webrtc",
"id": "whip-push",
"name": "Push to CDN",
"mode": "whip_client",
"whip_url": "https://whip.example.com/ingest/stream1",
"bearer_token": "my-auth-token"
}

WHEP Server mode — serve browser viewers:

{
"type": "webrtc",
"id": "whep-serve",
"name": "Browser Viewers",
"mode": "whep_server",
"max_viewers": 20,
"bearer_token": "viewer-auth-token"
}

Viewers POST an SDP offer to /api/v1/flows/{flow_id}/whep and receive an SDP answer.

FieldTypeRequiredDefaultDescription
typestringYes-Must be "webrtc".
idstringYes-Unique output ID.
namestringYes-Human-readable display name.
modestringNo"whip_client""whip_client" (push to endpoint) or "whep_server" (serve viewers).
whip_urlstringWHIP only-WHIP endpoint URL. Required for whip_client mode.
bearer_tokenstringNonullBearer token for authentication.
max_viewersintegerNo10Max concurrent viewers (WHEP server mode only, 1-100).
public_ipstringNonullPublic IP for ICE candidates (NAT traversal).
video_onlybooleanNofalseOnly send video (audio omitted). AAC sources automatically fall back to video-only.

Audio: Opus passthrough only. Opus flows natively on WebRTC paths. AAC sources fall back to video-only automatically (no C-library transcoding available).


Forward Error Correction parameters used by fec_decode (on RTP inputs) and fec_encode (on RTP outputs).

{
"columns": 10,
"rows": 10
}
FieldTypeRequiredRangeDescription
columnsintegerYes1-20L parameter: number of columns in the FEC matrix.
rowsintegerYes4-20D parameter: number of rows in the FEC matrix.

The FEC matrix protects columns x rows media packets with columns + rows parity packets. Larger matrices provide better protection at the cost of higher latency and bandwidth overhead.

Common configurations:

  • 5 x 5 — Low overhead, moderate protection
  • 10 x 10 — Good balance of overhead and protection
  • 20 x 20 — Maximum protection, higher latency

Both SRT input and SRT output support SMPTE 2022-7 hitless redundancy via a second SRT leg. The parent SRT config defines leg 1; the redundancy block defines leg 2.

For input: packets from both legs are merged using RTP sequence numbers, providing seamless failover if one path fails.

For output: packets are duplicated and sent on both legs simultaneously.

{
"redundancy": {
"mode": "listener",
"local_addr": "0.0.0.0:9001",
"remote_addr": null,
"latency_ms": 500,
"passphrase": "encryption-key",
"aes_key_len": 32
}
}
FieldTypeRequiredDefaultDescription
modestringYes-SRT mode for leg 2: "caller", "listener", or "rendezvous".
local_addrstringYes-Local bind address for leg 2.
remote_addrstringConditionalnullRemote address for leg 2 (required for caller/rendezvous).
latency_msintegerNo120SRT latency for leg 2.
passphrasestringNonullAES encryption passphrase for leg 2 (10-79 characters).
aes_key_lenintegerNo16AES key length for leg 2 (16, 24, or 32).
crypto_modestringNonullCipher mode for leg 2: "aes-ctr" or "aes-gcm".

Legs can use different SRT modes, different ports, different latency values, and even different encryption settings (though using the same settings is recommended for simplicity).


ModeInitiatorremote_addr requiredUse case
callerThis endpoint connects to a remote listenerYesSending to a known destination. Most common for outputs.
listenerThis endpoint waits for incoming connectionsNoAccepting streams from remote callers. Most common for inputs (ingest servers).
rendezvousBoth sides connect simultaneouslyYesNAT traversal. Both sides must use rendezvous mode and know each other’s address.

Command-line arguments override values from the config file. This is useful for deployment automation and containerization.

bilbycast-edge [OPTIONS]
Options:
-c, --config <PATH> Path to configuration file [default: ./config.json]
-p, --port <PORT> Override API listen port
-b, --bind <ADDRESS> Override API listen address
--monitor-port <PORT> Override monitor dashboard port
-l, --log-level <LEVEL> Log level: trace, debug, info, warn, error [default: info]
-h, --help Print help
-V, --version Print version
ArgumentConfig field overriddenExample
--portserver.listen_port--port 9443
--bindserver.listen_addr--bind 127.0.0.1
--monitor-portmonitor.listen_port--monitor-port 9091
--log-level(runtime only, not in config)--log-level debug

The log level can also be set via the RUST_LOG environment variable, which takes precedence over the --log-level argument when set. Supports fine-grained filtering (e.g., RUST_LOG=bilbycast_edge=debug,tower_http=info).

Examples:

Terminal window
# Use a specific config file
bilbycast-edge --config /etc/bilbycast/production.json
# Override port for containerized deployment
bilbycast-edge --config config.json --port 443 --bind 0.0.0.0
# Debug logging
bilbycast-edge --config config.json --log-level debug
# Fine-grained logging via environment
RUST_LOG=bilbycast_edge=debug,tower_http=info bilbycast-edge --config config.json

bilbycast-edge automatically persists configuration changes to disk when flows are modified through the API. Flow configs (including user parameters like SRT passphrases, RTSP credentials, RTMP keys) go to config.json, infrastructure secrets go to secrets.json:

  • Create flow (POST /api/v1/flows) — Appends the new flow and saves (flow parameters stay in config.json).
  • Update flow (PUT /api/v1/flows/{id}) — Replaces the flow in-place and saves.
  • Delete flow (DELETE /api/v1/flows/{id}) — Removes the flow and saves.
  • Add output (POST /api/v1/flows/{id}/outputs) — Appends the output and saves.
  • Remove output (DELETE /api/v1/flows/{id}/outputs/{oid}) — Removes the output and saves.
  • Replace config (PUT /api/v1/config) — Replaces the entire config and saves.
  • Get config (GET /api/v1/config) — Returns the config with infrastructure secrets stripped. Flow parameters (passphrases, credentials, keys) are included in the response.

All config saves use an atomic write strategy: both config.json and secrets.json are written to temporary files (.json.tmp), then atomically renamed to the target paths. This prevents corruption if the process is interrupted during a write. secrets.json is written with 0600 permissions (owner-only) on Unix.

If the config file does not exist when bilbycast-edge starts, an empty default configuration is used:

{
"version": 1,
"server": {
"listen_addr": "0.0.0.0",
"listen_port": 8080
},
"flows": []
}

Use POST /api/v1/config/reload to re-read both config.json and secrets.json from disk. This is useful after manual edits or after deploying new config files via external tooling (e.g., Ansible, Chef).


Minimal: RTP receive and forward (no auth)

Section titled “Minimal: RTP receive and forward (no auth)”
{
"version": 1,
"server": {
"listen_addr": "0.0.0.0",
"listen_port": 8080
},
"flows": [
{
"id": "passthrough",
"name": "RTP Passthrough",
"enabled": true,
"input": {
"type": "rtp",
"bind_addr": "0.0.0.0:5000"
},
"outputs": [
{
"type": "rtp",
"id": "out-1",
"name": "Forwarded Output",
"dest_addr": "192.168.1.50:5004"
}
]
}
]
}

Multicast receive with FEC and trust boundary filters

Section titled “Multicast receive with FEC and trust boundary filters”
{
"version": 1,
"server": {
"listen_addr": "0.0.0.0",
"listen_port": 8080
},
"flows": [
{
"id": "multicast-feed",
"name": "Multicast with FEC and Trust Boundary",
"enabled": true,
"input": {
"type": "rtp",
"bind_addr": "239.1.1.1:5000",
"interface_addr": "10.0.0.100",
"fec_decode": {
"columns": 10,
"rows": 10
},
"allowed_sources": ["10.0.0.1"],
"allowed_payload_types": [33],
"max_bitrate_mbps": 50.0,
"tr07_mode": true
},
"outputs": [
{
"type": "rtp",
"id": "local-out",
"name": "Local Multicast Output",
"dest_addr": "239.1.2.1:5004",
"interface_addr": "10.0.0.100",
"fec_encode": {
"columns": 10,
"rows": 10
},
"dscp": 46
}
]
}
]
}
{
"version": 1,
"server": {
"listen_addr": "0.0.0.0",
"listen_port": 8080
},
"flows": [
{
"id": "srt-redundant",
"name": "SRT with Hitless Redundancy",
"enabled": true,
"input": {
"type": "srt",
"mode": "listener",
"local_addr": "0.0.0.0:9000",
"latency_ms": 500,
"passphrase": "my-secure-passphrase-1234",
"aes_key_len": 32,
"redundancy": {
"mode": "listener",
"local_addr": "0.0.0.0:9001",
"latency_ms": 500,
"passphrase": "my-secure-passphrase-1234",
"aes_key_len": 32
}
},
"outputs": [
{
"type": "srt",
"id": "srt-out",
"name": "SRT Redundant Output",
"mode": "caller",
"local_addr": "0.0.0.0:0",
"remote_addr": "203.0.113.10:9000",
"latency_ms": 500,
"passphrase": "output-passphrase-1234567",
"aes_key_len": 32,
"redundancy": {
"mode": "caller",
"local_addr": "0.0.0.0:0",
"remote_addr": "203.0.113.11:9000",
"latency_ms": 500,
"passphrase": "output-passphrase-1234567",
"aes_key_len": 32
}
}
]
}
]
}

Multi-output: RTP to SRT, RTMP, and HLS simultaneously

Section titled “Multi-output: RTP to SRT, RTMP, and HLS simultaneously”
{
"version": 1,
"server": {
"listen_addr": "0.0.0.0",
"listen_port": 8080
},
"flows": [
{
"id": "multi-output",
"name": "Multi-Output Fan-Out",
"enabled": true,
"input": {
"type": "rtp",
"bind_addr": "239.1.1.1:5000",
"interface_addr": "192.168.1.100"
},
"outputs": [
{
"type": "rtp",
"id": "local",
"name": "Local Playout",
"dest_addr": "192.168.1.50:5004"
},
{
"type": "srt",
"id": "remote-srt",
"name": "Remote Site SRT",
"mode": "caller",
"local_addr": "0.0.0.0:0",
"remote_addr": "203.0.113.10:9000",
"latency_ms": 300
},
{
"type": "rtmp",
"id": "twitch",
"name": "Twitch",
"dest_url": "rtmp://live.twitch.tv/app",
"stream_key": "live_xxxxxxxxxxxx"
},
{
"type": "hls",
"id": "youtube-hls",
"name": "YouTube HLS",
"ingest_url": "https://a.upload.youtube.com/http_upload_hls?cid=xxxx",
"segment_duration_secs": 2.0
}
]
}
]
}

Full production config with TLS + auth + monitoring

Section titled “Full production config with TLS + auth + monitoring”
{
"version": 1,
"server": {
"listen_addr": "0.0.0.0",
"listen_port": 8443,
"tls": {
"cert_path": "/etc/bilbycast/cert.pem",
"key_path": "/etc/bilbycast/key.pem"
},
"auth": {
"enabled": true,
"jwt_secret": "K7nXp2qR8vF3mBwYd0hL5jZ1tA6gCeHsN9uIoP4xWkQrJfMaVbDcEiGyTlUwSzO",
"token_lifetime_secs": 3600,
"public_metrics": true,
"clients": [
{
"client_id": "ops-admin",
"client_secret": "admin-secret-change-me",
"role": "admin"
},
{
"client_id": "grafana",
"client_secret": "grafana-read-secret",
"role": "monitor"
}
]
}
},
"monitor": {
"listen_addr": "0.0.0.0",
"listen_port": 9090
},
"flows": [
{
"id": "main-feed",
"name": "Main Program Feed",
"enabled": true,
"input": {
"type": "rtp",
"bind_addr": "239.1.1.1:5000",
"interface_addr": "10.0.0.100",
"fec_decode": {
"columns": 10,
"rows": 10
}
},
"outputs": [
{
"type": "rtp",
"id": "local-playout",
"name": "Local Playout",
"dest_addr": "10.0.0.50:5004",
"dscp": 46
},
{
"type": "srt",
"id": "remote-site",
"name": "Remote Site",
"mode": "caller",
"local_addr": "0.0.0.0:0",
"remote_addr": "203.0.113.10:9000",
"latency_ms": 500,
"passphrase": "secure-transport-key-1234",
"aes_key_len": 32
}
]
}
]
}
{
"version": 1,
"server": {
"listen_addr": "0.0.0.0",
"listen_port": 8080
},
"flows": [
{
"id": "ipv6-mcast",
"name": "IPv6 Multicast Flow",
"enabled": true,
"input": {
"type": "rtp",
"bind_addr": "[ff7e::1]:5000",
"interface_addr": "::1"
},
"outputs": [
{
"type": "rtp",
"id": "ipv6-out",
"name": "IPv6 Output",
"dest_addr": "[ff7e::2]:5004",
"interface_addr": "::1"
}
]
}
]
}