Skip to content

API Reference

Complete REST API reference for bilbycast-edge. The API server listens on the address and port configured in server.listen_addr and server.listen_port (default 0.0.0.0:8080).

All successful responses use a standard envelope:

{
"success": true,
"data": { ... }
}

All error responses use the same envelope without data:

{
"success": false,
"error": "Human-readable error message"
}


Lightweight health check suitable for load balancers, orchestrators, and monitoring probes. This endpoint is always public (no authentication required).

Response:

{
"status": "ok",
"version": "0.1.0",
"uptime_secs": 3661,
"active_flows": 2,
"total_flows": 3
}
FieldTypeDescription
statusstringAlways "ok" when the server is responsive
versionstringApplication version from Cargo.toml
uptime_secsintegerSeconds since the application started
active_flowsintegerNumber of flows currently running
total_flowsintegerTotal flows defined in configuration

curl example:

Terminal window
curl http://localhost:8080/health

Browser-based initial provisioning for edge nodes deployed on COTS hardware. All setup endpoints are public (no authentication required). Access is controlled by the setup_enabled config flag (default: true).

Serves the setup wizard HTML page. Returns the wizard form when setup_enabled is true, or a “Setup Disabled” page when false.

Returns the current setup-relevant configuration as JSON for pre-filling the form. The registration_token is always null in responses — secrets are never exposed via this endpoint.

Response:

{
"listen_addr": "0.0.0.0",
"listen_port": 8080,
"manager_url": null,
"accept_self_signed_cert": false,
"registration_token": null,
"device_name": null,
"setup_enabled": true
}

Validates and saves setup configuration. Returns 403 if setup_enabled is false.

Request body:

{
"listen_addr": "0.0.0.0",
"listen_port": 8080,
"manager_url": "wss://manager.example.com:8443/ws/node",
"accept_self_signed_cert": false,
"registration_token": "token-from-manager",
"device_name": "Studio-A Encoder"
}
FieldTypeRequiredConstraints
listen_addrstringNoAPI server bind address
listen_portu16No1-65535
manager_urlstringYesMust start with wss://, max 2048 chars
accept_self_signed_certboolNoDefault: false
registration_tokenstringNoMax 4096 chars
device_namestringNoMax 256 chars

Success response (200):

{
"success": true,
"message": "Configuration saved. Restart the bilbycast-edge service to apply the new settings."
}

Error response (400/403):

{
"success": false,
"error": "Manager URL must start with wss:// (TLS required)"
}

OAuth 2.0 Client Credentials token endpoint. Always public (no Bearer token required). Returns a signed JWT that must be included as a Bearer token in subsequent requests to protected endpoints.

Accepts both application/x-www-form-urlencoded and application/json request bodies.

Request body (JSON):

{
"grant_type": "client_credentials",
"client_id": "admin-client",
"client_secret": "super-secret-admin-password-here"
}

Request body (form-urlencoded):

grant_type=client_credentials&client_id=admin-client&client_secret=super-secret-admin-password-here
FieldTypeRequiredDescription
grant_typestringYesMust be "client_credentials"
client_idstringYesRegistered client identifier
client_secretstringYesClient secret

Success response (200):

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbi1jbGllbnQiLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE3MDk4MjAwMDAsImV4cCI6MTcwOTgyMzYwMCwiaXNzIjoiYmlsYnljYXN0LWVkZ2UifQ.signature",
"token_type": "bearer",
"expires_in": 3600,
"role": "admin"
}
FieldTypeDescription
access_tokenstringSigned HS256 JWT token
token_typestringAlways "bearer"
expires_inintegerToken lifetime in seconds
rolestringRole granted: "admin" or "monitor"

Error responses:

StatusCondition
400Auth not enabled, unsupported grant_type, invalid credentials, unparseable body

curl examples:

Terminal window
# JSON body
curl -X POST http://localhost:8080/oauth/token \
-H "Content-Type: application/json" \
-d '{"grant_type":"client_credentials","client_id":"admin-client","client_secret":"super-secret-admin-password-here"}'
# Form-urlencoded body
curl -X POST http://localhost:8080/oauth/token \
-d "grant_type=client_credentials&client_id=admin-client&client_secret=super-secret-admin-password-here"

JWT Claims structure:

The returned JWT contains these claims:

ClaimTypeDescription
substringThe client_id
rolestring"admin" or "monitor"
iatintegerIssued-at timestamp (Unix epoch seconds)
expintegerExpiration timestamp (Unix epoch seconds)
issstringAlways "bilbycast-edge"

List all configured flows. Returns a summary for each flow without full input/output details.

Auth: Requires valid JWT (any role: admin or monitor).

Response (200):

{
"success": true,
"data": {
"flows": [
{
"id": "main-feed",
"name": "Main Program Feed",
"enabled": true,
"input_type": "rtp",
"output_count": 3
},
{
"id": "backup-srt",
"name": "SRT Backup Path",
"enabled": true,
"input_type": "srt",
"output_count": 1
}
]
}
}
FieldTypeDescription
flows[].idstringUnique flow identifier
flows[].namestringHuman-readable display name
flows[].enabledbooleanWhether the flow is enabled in config
flows[].input_typestring"rtp" or "srt"
flows[].output_countintegerNumber of configured outputs

curl example:

Terminal window
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/flows

Retrieve the full configuration of a single flow, including all input and output details.

Auth: Requires valid JWT (any role).

Path parameters:

ParameterTypeDescription
flow_idstringUnique identifier of the flow

Response (200):

{
"success": true,
"data": {
"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
}
},
"outputs": [
{
"type": "rtp",
"id": "rtp-out-1",
"name": "Local Playout",
"dest_addr": "192.168.1.50:5004",
"dscp": 46
},
{
"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
}
]
}
}

Error responses:

StatusCondition
404Flow not found

curl example:

Terminal window
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/flows/main-feed

Create a new flow. The flow is validated, persisted to the config file, and (if enabled: true) started immediately.

Auth: Requires admin role.

Request body:

{
"id": "new-flow",
"name": "New Feed",
"enabled": true,
"input": {
"type": "rtp",
"bind_addr": "0.0.0.0:5000"
},
"outputs": [
{
"type": "rtp",
"id": "out-1",
"name": "Output 1",
"dest_addr": "192.168.1.50:5004"
}
]
}

Response (200):

Returns the created flow configuration in the standard envelope.

Error responses:

StatusCondition
400Validation failure (invalid addresses, empty ID/name, bad FEC params)
409A flow with the same ID already exists
500Failed to persist config to disk

curl example:

Terminal window
curl -X POST http://localhost:8080/api/v1/flows \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "new-flow",
"name": "New Feed",
"enabled": true,
"input": {
"type": "rtp",
"bind_addr": "0.0.0.0:5000"
},
"outputs": [{
"type": "rtp",
"id": "out-1",
"name": "Output 1",
"dest_addr": "192.168.1.50:5004"
}]
}'

Replace an existing flow’s configuration. The flow ID in the path takes precedence over the id field in the body. If the flow is running, it is stopped, updated, and restarted (if enabled).

Auth: Requires admin role.

Path parameters:

ParameterTypeDescription
flow_idstringUnique identifier of the flow to update

Request body:

Full FlowConfig JSON (same structure as POST /api/v1/flows).

Response (200):

Returns the updated flow configuration.

Error responses:

StatusCondition
400Validation failure
404Flow not found
500Failed to persist config to disk

curl example:

Terminal window
curl -X PUT http://localhost:8080/api/v1/flows/main-feed \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "main-feed",
"name": "Main Feed (Updated)",
"enabled": true,
"input": {
"type": "rtp",
"bind_addr": "239.1.1.1:5000",
"interface_addr": "192.168.1.100"
},
"outputs": [{
"type": "rtp",
"id": "out-1",
"name": "Output 1",
"dest_addr": "192.168.1.50:5004"
}]
}'

Delete a flow. Stops the flow if running, removes it from configuration, and persists the change.

Auth: Requires admin role.

Path parameters:

ParameterTypeDescription
flow_idstringUnique identifier of the flow to delete

Response (200):

{
"success": true,
"data": null
}

Error responses:

StatusCondition
404Flow not found
500Failed to persist config to disk

curl example:

Terminal window
curl -X DELETE http://localhost:8080/api/v1/flows/old-flow \
-H "Authorization: Bearer $TOKEN"

Start a stopped flow. Reads the flow configuration and starts it in the engine.

Auth: Requires admin role.

Path parameters:

ParameterTypeDescription
flow_idstringUnique identifier of the flow to start

Response (200):

{
"success": true,
"data": null
}

Error responses:

StatusCondition
404Flow not found in configuration
409Flow is already running
500Engine failed to start the flow

curl example:

Terminal window
curl -X POST http://localhost:8080/api/v1/flows/main-feed/start \
-H "Authorization: Bearer $TOKEN"

Stop a running flow. The flow configuration is preserved; only the runtime instance is torn down. The flow can be restarted later.

Auth: Requires admin role.

Path parameters:

ParameterTypeDescription
flow_idstringUnique identifier of the flow to stop

Response (200):

{
"success": true,
"data": null
}

Error responses:

StatusCondition
404Flow not found in configuration
409Flow is not currently running
500Engine failed to stop the flow

curl example:

Terminal window
curl -X POST http://localhost:8080/api/v1/flows/main-feed/stop \
-H "Authorization: Bearer $TOKEN"

Restart a flow (stop + start). Destroys the running instance (if any) and creates a fresh one from the current configuration. Useful for picking up config changes or recovering from transient errors.

Auth: Requires admin role.

Path parameters:

ParameterTypeDescription
flow_idstringUnique identifier of the flow to restart

Response (200):

{
"success": true,
"data": null
}

Error responses:

StatusCondition
404Flow not found in configuration
500Engine failed to create the new flow instance

curl example:

Terminal window
curl -X POST http://localhost:8080/api/v1/flows/main-feed/restart \
-H "Authorization: Bearer $TOKEN"

Add a new output to an existing flow. The output is validated, appended to the flow’s output list, and persisted. If the flow is currently running, the output is hot-added without stopping the flow.

Auth: Requires admin role.

Path parameters:

ParameterTypeDescription
flow_idstringFlow to add the output to

Request body:

An OutputConfig object. The type field determines the output kind. See the Configuration Guide for all output types and their fields.

{
"type": "srt",
"id": "srt-backup",
"name": "SRT Backup Output",
"mode": "caller",
"local_addr": "0.0.0.0:0",
"remote_addr": "203.0.113.20:9000",
"latency_ms": 300
}

Response (200):

Returns the created output configuration.

Error responses:

StatusCondition
400Output validation failure
404Flow not found
409An output with the same ID already exists in the flow
500Failed to persist config to disk

curl example:

Terminal window
curl -X POST http://localhost:8080/api/v1/flows/main-feed/outputs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "srt",
"id": "srt-backup",
"name": "SRT Backup Output",
"mode": "caller",
"local_addr": "0.0.0.0:0",
"remote_addr": "203.0.113.20:9000",
"latency_ms": 300
}'

DELETE /api/v1/flows/{flow_id}/outputs/{output_id}

Section titled “DELETE /api/v1/flows/{flow_id}/outputs/{output_id}”

Remove an output from a flow. If the flow is running, the output is hot-removed from the engine first, then removed from config and persisted.

Auth: Requires admin role.

Path parameters:

ParameterTypeDescription
flow_idstringFlow containing the output
output_idstringOutput to remove

Response (200):

{
"success": true,
"data": null
}

Error responses:

StatusCondition
404Flow not found, or output not found within the flow
500Failed to persist config to disk

curl example:

Terminal window
curl -X DELETE http://localhost:8080/api/v1/flows/main-feed/outputs/srt-backup \
-H "Authorization: Bearer $TOKEN"

Retrieve aggregated system-wide and per-flow statistics. Running flows include live counters; configured-but-stopped flows are included with zeroed counters.

Auth: Requires valid JWT (any role).

Response (200):

{
"success": true,
"data": {
"system": {
"uptime_secs": 86400,
"total_flows": 3,
"active_flows": 2,
"version": "0.1.0"
},
"flows": [
{
"flow_id": "main-feed",
"flow_name": "Main Program Feed",
"state": "Running",
"health": "Healthy",
"uptime_secs": 86350,
"input": {
"input_type": "udp",
"state": "receiving",
"packets_received": 15234567,
"bytes_received": 20113628844,
"bitrate_bps": 50000000,
"packets_lost": 12,
"packets_filtered": 0,
"packets_recovered_fec": 8,
"redundancy_switches": 0,
"srt_stats": null,
"srt_leg2_stats": null
},
"outputs": [
{
"output_id": "rtp-out-1",
"output_name": "Local Playout",
"output_type": "udp",
"state": "active",
"packets_sent": 15234555,
"bytes_sent": 20113612680,
"bitrate_bps": 50000000,
"packets_dropped": 0,
"fec_packets_sent": 3046911,
"srt_stats": null,
"srt_leg2_stats": null
}
],
"tr101290": {
"ts_packets_analyzed": 106641969,
"pat_count": 17280,
"pmt_count": 17280,
"sync_loss_count": 0,
"sync_byte_errors": 0,
"cc_errors": 0,
"pat_errors": 0,
"pmt_errors": 0,
"tei_errors": 0,
"pcr_discontinuity_errors": 0,
"pcr_accuracy_errors": 0,
"priority1_ok": true,
"priority2_ok": true,
"tr07_compliant": false,
"jpeg_xs_pid": null
},
"media_analysis": {
"protocol": "srt",
"payload_format": "raw_ts",
"fec": null,
"redundancy": null,
"program_count": 1,
"video_streams": [
{
"pid": 256,
"codec": "H.264/AVC",
"stream_type": 27,
"resolution": "1920x1080",
"frame_rate": 29.97,
"profile": "High",
"level": "4.0",
"bitrate_bps": 5000000
}
],
"audio_streams": [
{
"pid": 257,
"codec": "AAC-LC",
"stream_type": 15,
"sample_rate_hz": 48000,
"channels": 2,
"language": "eng",
"bitrate_bps": 128000
}
],
"total_bitrate_bps": 5200000
},
"iat": {
"min_us": 120.5,
"max_us": 180.3,
"avg_us": 142.7
},
"pdv_jitter_us": 15.2
}
]
}
}

System stats fields:

FieldTypeDescription
uptime_secsintegerApplication uptime in seconds
total_flowsintegerTotal configured flows
active_flowsintegerCurrently running flows
versionstringApplication version

Per-flow fields:

FieldTypeDescription
flow_idstringFlow identifier
flow_namestringDisplay name
statestring"Idle", "Starting", "Running", "Error", or "Stopped"
healthstring"Healthy", "Warning", "Error", or "Critical" (RP 2129 M6)
uptime_secsintegerSeconds since the flow was started
inputobjectInput leg statistics (see below)
outputsarrayPer-output statistics (see below)
tr101290object/nullTR-101290 analysis (present when running)
media_analysisobject/nullMedia content analysis — codec, resolution, frame rate, audio format, per-PID bitrate (present when running and media_analysis config is true)
iatobject/nullInter-arrival time stats in microseconds
pdv_jitter_usfloat/nullPacket delivery variation (jitter) in microseconds

Input stats fields:

FieldTypeDescription
input_typestring"rtp", "srt", "rtmp", "rtsp", "webrtc", or "whep"
statestringConnection state (e.g., "receiving", "connecting")
packets_receivedintegerTotal RTP packets received
bytes_receivedintegerTotal bytes received
bitrate_bpsintegerCurrent bitrate in bits/sec
packets_lostintegerPackets lost (sequence gaps)
packets_filteredintegerPackets dropped by ingress filters
packets_recovered_fecintegerPackets recovered via FEC
srt_statsobject/nullSRT leg 1 stats (if SRT input)
srt_leg2_statsobject/nullSRT leg 2 stats (if redundancy enabled)
redundancy_switchesintegerSMPTE 2022-7 leg switch count

Output stats fields:

FieldTypeDescription
output_idstringOutput identifier
output_namestringDisplay name
output_typestring"udp", "srt", "rtmp", "hls", or "webrtc"
statestringConnection state
packets_sentintegerTotal packets sent
bytes_sentintegerTotal bytes sent
bitrate_bpsintegerCurrent bitrate in bits/sec
packets_droppedintegerPackets dropped (channel full)
fec_packets_sentintegerFEC packets sent
srt_statsobject/nullSRT leg 1 stats (if SRT output)
srt_leg2_statsobject/nullSRT leg 2 stats (if redundancy)

SRT leg stats fields:

FieldTypeDescription
statestringSRT socket state (e.g., "connected", "broken")
rtt_msfloatRound-trip time in milliseconds
send_rate_mbpsfloatEstimated send rate in Mbps
recv_rate_mbpsfloatEstimated receive rate in Mbps
pkt_loss_totalintegerTotal packets lost
pkt_retransmit_totalintegerTotal retransmitted packets
uptime_msintegerSocket uptime in milliseconds

curl example:

Terminal window
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/stats

Retrieve statistics for a single flow. Returns live stats if running, or zeroed stats if the flow is configured but stopped.

Auth: Requires valid JWT (any role).

Path parameters:

ParameterTypeDescription
flow_idstringFlow to retrieve stats for

Response (200):

Returns a single FlowStats object (same structure as entries in the flows array above).

Error responses:

StatusCondition
404Flow not found in engine or configuration

curl example:

Terminal window
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/stats/main-feed

Retrieve the running application configuration with infrastructure secrets stripped. Returns the operational config including flow definitions with all user-configured parameters (SRT passphrases, RTSP credentials, RTMP stream keys, bearer tokens). Infrastructure secrets (node_secret, tunnel encryption keys, JWT secrets, client credentials, TLS config) are never included.

Auth: Requires valid JWT (any role).

Response (200):

{
"success": true,
"data": {
"version": 1,
"server": {
"listen_addr": "0.0.0.0",
"listen_port": 8080
},
"monitor": {
"listen_addr": "0.0.0.0",
"listen_port": 9090
},
"flows": [ ... ]
}
}

curl example:

Terminal window
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/config

Replace the entire application configuration atomically. Stops all running flows, replaces the in-memory config, persists to disk (flow configs including user parameters to config.json, infrastructure secrets to secrets.json), and starts all flows with enabled: true.

Auth: Requires admin role.

Request body:

A complete AppConfig JSON object (see Configuration Guide). Flow parameters (SRT passphrases, RTSP credentials, RTMP keys, etc.) are stored in config.json. Infrastructure secrets (auth config, TLS) are stored in secrets.json.

Response (200):

Returns the new configuration with infrastructure secrets stripped (flow parameters preserved).

Error responses:

StatusCondition
400Config validation failure (duplicate flow IDs, invalid addresses, etc.)
500Failed to persist config or individual flows failed to start

curl example:

Terminal window
curl -X PUT http://localhost:8080/api/v1/config \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @config.json

Reload configuration from disk (config.json + secrets.json). Stops all running flows, reads and validates both files, merges secrets into the config, replaces the in-memory state, and starts all enabled flows. Useful after manual edits to the config files.

Auth: Requires admin role.

Request body: None.

Response (200):

Returns the reloaded configuration with secrets stripped.

Error responses:

StatusCondition
400Loaded config fails validation
500Config file cannot be read or parsed

curl example:

Terminal window
curl -X POST http://localhost:8080/api/v1/config/reload \
-H "Authorization: Bearer $TOKEN"

Prometheus-compatible metrics endpoint. Returns metrics in the Prometheus text exposition format (text/plain; version=0.0.4).

Auth: Public by default when public_metrics: true (the default). When public_metrics: false, requires a valid JWT (any role).

Metric families:

Application-level gauges:

MetricTypeDescription
bilbycast_edge_info{version="..."}gaugeApplication version (always 1)
bilbycast_edge_uptime_secondsgaugeSeconds since startup
bilbycast_edge_flows_totalgaugeTotal configured flows
bilbycast_edge_flows_activegaugeCurrently running flows

Per-flow input metrics (labeled by flow_id):

MetricTypeDescription
bilbycast_edge_flow_input_packets_totalcounterRTP packets received
bilbycast_edge_flow_input_bytes_totalcounterBytes received
bilbycast_edge_flow_input_bitrate_bpsgaugeInput bitrate (bits/sec)
bilbycast_edge_flow_input_packets_lostcounterPackets lost
bilbycast_edge_flow_input_fec_recovered_totalcounterPackets recovered via FEC
bilbycast_edge_flow_input_redundancy_switches_totalcounterSMPTE 2022-7 leg switches
bilbycast_edge_flow_input_packets_filteredcounterPackets dropped by ingress filters
bilbycast_edge_flow_pdv_jitter_usgaugePDV jitter in microseconds
bilbycast_edge_flow_iat_avg_usgaugeAverage inter-arrival time in microseconds

Per-output metrics (labeled by flow_id, output_id):

MetricTypeDescription
bilbycast_edge_flow_output_packets_totalcounterPackets sent
bilbycast_edge_flow_output_bytes_totalcounterBytes sent
bilbycast_edge_flow_output_bitrate_bpsgaugeOutput bitrate (bits/sec)
bilbycast_edge_flow_output_packets_droppedcounterPackets dropped
bilbycast_edge_flow_output_fec_sent_totalcounterFEC packets sent

SRT metrics (labeled by flow_id, optionally output_id and leg):

MetricTypeDescription
bilbycast_edge_srt_rtt_msgaugeSRT round-trip time in ms
bilbycast_edge_srt_loss_totalcounterSRT total packet loss

TR-101290 metrics (labeled by flow_id):

MetricTypeDescription
bilbycast_edge_tr101290_ts_packets_totalcounterTS packets analyzed
bilbycast_edge_tr101290_sync_byte_errors_totalcounterSync byte errors
bilbycast_edge_tr101290_cc_errors_totalcounterContinuity counter errors
bilbycast_edge_tr101290_pat_errors_totalcounterPAT timeout errors
bilbycast_edge_tr101290_pmt_errors_totalcounterPMT timeout errors
bilbycast_edge_tr101290_tei_errors_totalcounterTransport error indicator errors
bilbycast_edge_tr101290_pcr_discontinuity_errors_totalcounterPCR discontinuity errors
bilbycast_edge_tr101290_pcr_accuracy_errors_totalcounterPCR accuracy errors

Media analysis metrics (labeled by flow_id and pid):

MetricTypeDescription
bilbycast_edge_media_video_infoinfoVideo stream info (labels: codec, resolution, profile, level)
bilbycast_edge_media_video_framerategaugeVideo frame rate in fps
bilbycast_edge_media_audio_infoinfoAudio stream info (labels: codec, sample_rate, channels, language)
bilbycast_edge_media_pid_bitrate_bpsgaugePer-PID bitrate in bits/sec (label: type = video or audio)
bilbycast_edge_media_total_bitrate_bpsgaugeTotal TS bitrate in bits/sec

Only metrics for currently running flows are emitted.

curl example:

Terminal window
curl http://localhost:8080/metrics

List all active IP tunnels.

Auth: Requires valid JWT (any role) when auth is enabled.

Response (200 OK):

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

Get status of a specific tunnel.

Auth: Requires valid JWT (any role) when auth is enabled.

Response (200 OK):

{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Stadium to Studio",
"protocol": "udp",
"mode": "relay",
"direction": "egress",
"state": "Connected",
"local_addr": "0.0.0.0:9000",
"relay_addr": "relay.example.com:4433"
}

Response (404 Not Found):

{
"error": "Tunnel not found"
}

Create a new IP tunnel. The tunnel configuration is validated before creation.

Auth: Requires valid JWT with admin role when auth is enabled.

Request body: A TunnelConfig JSON object. See Tunnel Configuration for all fields.

Example — relay mode UDP tunnel:

{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Stadium to Studio",
"enabled": true,
"protocol": "udp",
"mode": "relay",
"direction": "egress",
"local_addr": "0.0.0.0:9000",
"relay_addr": "relay.example.com:4433",
"tunnel_encryption_key": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}

Response (201 Created):

{
"status": "created"
}

Response (400 Bad Request):

{
"error": "tunnel_encryption_key is required for relay mode"
}

Destroy a tunnel and clean up its connections.

Auth: Requires valid JWT with admin role when auth is enabled.

Response (200 OK):

{
"status": "deleted"
}

Response (404 Not Found):

{
"error": "Tunnel not found"
}

WebSocket endpoint for real-time statistics streaming. Upgrades the HTTP connection to a WebSocket and pushes JSON stats messages at approximately 1-second intervals.

Auth: Requires valid JWT (any role) when auth is enabled. The token can be passed as a standard Authorization: Bearer header on the upgrade request or as a query parameter for browser clients (see Security Guide).

Protocol: This is a server-push channel. The server sends JSON text frames; client-to-server messages are ignored.

Message format:

Each WebSocket text frame contains a JSON array of per-flow stats snapshots:

[
{
"flow_id": "main-feed",
"flow_name": "Main Program Feed",
"state": "Running",
"health": "Healthy",
"uptime_secs": 86350,
"input": { ... },
"outputs": [ ... ],
"tr101290": { ... },
"iat": { ... },
"pdv_jitter_us": 15.2
}
]

The structure of each flow stats object is identical to the entries in GET /api/v1/stats.

Connection behavior:

  • Messages are broadcast on a shared channel. If a client falls behind, messages are skipped (lagged) rather than buffered.
  • The connection closes when the client sends a Close frame, disconnects, or when the broadcast channel is closed.

JavaScript example:

const ws = new WebSocket("ws://localhost:8080/api/v1/ws/stats");
ws.onmessage = (event) => {
const flows = JSON.parse(event.data);
flows.forEach(flow => {
console.log(`${flow.flow_id}: ${flow.input.bitrate_bps} bps, health=${flow.health}`);
});
};
ws.onerror = (err) => console.error("WebSocket error:", err);
ws.onclose = () => console.log("WebSocket closed");

curl example (wscat):

Terminal window
wscat -c "ws://localhost:8080/api/v1/ws/stats" \
-H "Authorization: Bearer $TOKEN"

All API errors return a JSON body with "success": false and an "error" message string.

HTTP StatusError TypeDescription
400Bad RequestRequest body failed validation, malformed JSON, unsupported grant type
401UnauthorizedMissing or invalid Authorization header, expired token, bad signature
403ForbiddenValid token but insufficient role (admin required)
404Not FoundFlow or output does not exist
409ConflictResource already exists, flow already running/stopped
500Internal Server ErrorDisk I/O failure, engine error, JWT signing failure

Example error responses:

// 401 Unauthorized
{
"success": false,
"error": "missing Authorization header"
}
// 403 Forbidden
{
"success": false,
"error": "admin role required"
}
// 404 Not Found
{
"success": false,
"error": "Flow 'nonexistent' not found"
}
// 409 Conflict
{
"success": false,
"error": "Flow 'main-feed' is already running"
}

MethodPathAuthRoleDescription
GET/healthNo-Health check
POST/oauth/tokenNo-Get JWT token
GET/metricsConfigurableanyPrometheus metrics
GET/api/v1/flowsYesanyList all flows
GET/api/v1/flows/{flow_id}YesanyGet flow details
POST/api/v1/flowsYesadminCreate flow
PUT/api/v1/flows/{flow_id}YesadminUpdate flow
DELETE/api/v1/flows/{flow_id}YesadminDelete flow
POST/api/v1/flows/{flow_id}/startYesadminStart flow
POST/api/v1/flows/{flow_id}/stopYesadminStop flow
POST/api/v1/flows/{flow_id}/restartYesadminRestart flow
POST/api/v1/flows/{flow_id}/outputsYesadminAdd output
DELETE/api/v1/flows/{flow_id}/outputs/{output_id}YesadminRemove output
POST/api/v1/flows/{flow_id}/whipYesadminWHIP: Accept WebRTC publisher (SDP offer → answer)
DELETE/api/v1/flows/{flow_id}/whip/{session_id}YesadminWHIP: Disconnect publisher
POST/api/v1/flows/{flow_id}/whepYesadminWHEP: Accept WebRTC viewer (SDP offer → answer)
DELETE/api/v1/flows/{flow_id}/whep/{session_id}YesadminWHEP: Disconnect viewer
GET/api/v1/tunnelsYesanyList all tunnels
GET/api/v1/tunnels/{id}YesanyGet tunnel status
POST/api/v1/tunnelsYesadminCreate tunnel
DELETE/api/v1/tunnels/{id}YesadminDelete tunnel
GET/api/v1/statsYesanyAll statistics
GET/api/v1/stats/{flow_id}YesanySingle flow stats
GET/api/v1/configYesanyGet running config
PUT/api/v1/configYesadminReplace entire config
POST/api/v1/config/reloadYesadminReload config from disk
GET/api/v1/ws/statsYesanyWebSocket stats stream
GET/x-nmos/node/v1.3/No-NMOS IS-04: Node API root
GET/x-nmos/node/v1.3/selfNo-NMOS IS-04: Node resource
GET/x-nmos/node/v1.3/devices/No-NMOS IS-04: List devices
GET/x-nmos/node/v1.3/devices/{id}No-NMOS IS-04: Get device
GET/x-nmos/node/v1.3/sources/No-NMOS IS-04: List sources
GET/x-nmos/node/v1.3/sources/{id}No-NMOS IS-04: Get source
GET/x-nmos/node/v1.3/flows/No-NMOS IS-04: List flows
GET/x-nmos/node/v1.3/flows/{id}No-NMOS IS-04: Get flow
GET/x-nmos/node/v1.3/senders/No-NMOS IS-04: List senders
GET/x-nmos/node/v1.3/senders/{id}No-NMOS IS-04: Get sender
GET/x-nmos/node/v1.3/receivers/No-NMOS IS-04: List receivers
GET/x-nmos/node/v1.3/receivers/{id}No-NMOS IS-04: Get receiver
GET/x-nmos/connection/v1.1/single/senders/No-NMOS IS-05: List senders
GET/x-nmos/connection/v1.1/single/senders/{id}/stagedNo-NMOS IS-05: Get staged params
PATCH/x-nmos/connection/v1.1/single/senders/{id}/stagedNo-NMOS IS-05: Update staged + activate
GET/x-nmos/connection/v1.1/single/senders/{id}/activeNo-NMOS IS-05: Get active params
GET/x-nmos/connection/v1.1/single/senders/{id}/transporttypeNo-NMOS IS-05: Get transport type
GET/x-nmos/connection/v1.1/single/senders/{id}/constraintsNo-NMOS IS-05: Get constraints
GET/x-nmos/connection/v1.1/single/receivers/No-NMOS IS-05: List receivers
GET/x-nmos/connection/v1.1/single/receivers/{id}/stagedNo-NMOS IS-05: Get staged params
PATCH/x-nmos/connection/v1.1/single/receivers/{id}/stagedNo-NMOS IS-05: Update staged + activate
GET/x-nmos/connection/v1.1/single/receivers/{id}/activeNo-NMOS IS-05: Get active params
GET/x-nmos/connection/v1.1/single/receivers/{id}/transporttypeNo-NMOS IS-05: Get transport type
GET/x-nmos/connection/v1.1/single/receivers/{id}/constraintsNo-NMOS IS-05: Get constraints