Skip to content

API Reference

Complete REST API reference for bilbycast-edge. The API server binds the addresses configured in server.listen_addrs (dual-stack, precedence over the legacy single server.listen_addr) on port server.listen_port (default 8080). New installs bind loopback only — 127.0.0.1 and [::1] — so the API is not reachable off-box out of the box. LAN exposure requires launching with --bind-addrs 0.0.0.0,[::] (or setting server.listen_addrs accordingly) and enabling authentication.

Inputs, outputs, and flows are independent first-class entities. Inputs and outputs are top-level, individually CRUDable resources; a flow references them by ID via its input_ids and output_ids arrays rather than embedding their definitions. Internally the edge resolves those IDs into a ResolvedFlow (dereferencing every input/output) before the engine runs the flow.

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,
"manager": {
"enabled": true,
"connected": true,
"reconnecting": false
}
}
Field Type Description
status string Always "ok" when the server is responsive. It reflects the edge’s own health — it is deliberately not flipped to an error state when the manager link is down
version string Application version from Cargo.toml
uptime_secs integer Seconds since the application started
active_flows integer Number of flows currently running
total_flows integer Total flows defined in configuration
manager object Device-local view of the manager link, so an operator standing at the box can see a lost link without reading logs

The manager sub-object:

Field Type Description
enabled boolean Whether a manager client is configured on this node at all. false means “not managed”, not “disconnected”
connected boolean Whether an authenticated WebSocket session to the manager is currently live
reconnecting boolean true when a manager is enabled but the link is down — the client loop is always retrying in that state
disconnected_secs integer Seconds since the link went down. Omitted while connected, and also before the first successful connection (boot — render that as “connecting”)

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"
}
Field Type Required Constraints
listen_addr string No API server bind address
listen_port u16 No 1-65535
manager_url string Yes Must start with wss://, max 2048 chars
accept_self_signed_cert bool No Default: false
registration_token string No Max 4096 chars
device_name string No Max 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
Field Type Required Description
grant_type string Yes Must be "client_credentials"
client_id string Yes Registered client identifier
client_secret string Yes Client secret

Success response (200):

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbi1jbGllbnQiLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE3MDk4MjAwMDAsImV4cCI6MTcwOTgyMzYwMCwiaXNzIjoiYmlsYnljYXN0LWVkZ2UifQ.signature",
"token_type": "bearer",
"expires_in": 3600,
"role": "admin"
}
Field Type Description
access_token string Signed HS256 JWT token
token_type string Always "bearer"
expires_in integer Token lifetime in seconds
role string Role granted: "admin" or "monitor"

Error responses:

Status Condition
400 Auth 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:

Claim Type Description
sub string The client_id
role string "admin" or "monitor"
iat integer Issued-at timestamp (Unix epoch seconds)
exp integer Expiration timestamp (Unix epoch seconds)
iss string Always "bilbycast-edge"

Inputs are top-level, first-class entities, independent of any flow. A flow references inputs by ID via its input_ids array; the same input can be referenced by more than one flow. Create/read/update/delete them here, then wire them into flows.

The request/response body is an InputDefinition whose type field selects the variant. type is a free-form string: rtp, udp, rtp_audio, srt, rist, rtmp, rtsp, webrtc, whep, bonded, media_player, test_pattern, replay, st2110_20, st2110_23, st2110_30, st2110_31, st2110_40 (plus mosaic under the multiviewer feature, which every published release artefact carries — see Multiviewer — mxl_video / mxl_audio / mxl_anc under the mxl feature, and sdi under sdi-decklink). See the Configuration Guide for per-type fields.

List all configured inputs.

Auth: Requires valid JWT (any role).

Create a new input. The definition is validated and persisted.

Auth: Requires admin role.

Request body:

{
"type": "rtp",
"id": "rtp-in-1",
"name": "Main RTP Input",
"bind_addr": "239.1.1.1:5000",
"interface_addr": "192.168.1.100",
"fec_decode": { "columns": 10, "rows": 10 }
}

Error responses:

Status Condition
400 Validation failure (invalid addresses, empty ID/name, bad FEC params)
409 An input with the same ID already exists
500 Failed to persist config to disk

Retrieve a single input definition.

Auth: Requires valid JWT (any role). Returns 404 if not found.

Replace an input definition. Flows referencing this input pick up the change on their next (re)start; hot-swappable changes are applied surgically without stopping running outputs where possible.

Auth: Requires admin role.

Delete an input. Fails if the input is still referenced by a flow.

Auth: Requires admin role.

Error responses:

Status Condition
404 Input not found
409 Input is still referenced by one or more flows
500 Failed to persist config to disk

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
}
]
}
}
Field Type Description
flows[].id string Unique flow identifier
flows[].name string Human-readable display name
flows[].enabled boolean Whether the flow is enabled in config
flows[].input_type string Type of the flow’s first referenced input. A free-form string — one of rtp, udp, rtp_audio, srt, rist, rtmp, rtsp, webrtc, whep, bonded, media_player, test_pattern, replay, st2110_20, st2110_23, st2110_30, st2110_31, st2110_40 (plus mosaic under the multiviewer feature, mxl_video / mxl_audio / mxl_anc under the mxl feature, and sdi under sdi-decklink).
flows[].output_count integer Number of outputs referenced by the flow

curl example:

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

Retrieve the full configuration of a single flow. A flow references its input(s) and output(s) by ID — the returned object carries input_ids and output_ids, not embedded definitions. Fetch the referenced entities from GET /api/v1/inputs/{id} and GET /api/v1/outputs/{id}.

Auth: Requires valid JWT (any role).

Path parameters:

Parameter Type Description
flow_id string Unique identifier of the flow

Response (200):

{
"success": true,
"data": {
"id": "main-feed",
"name": "Main Program Feed",
"enabled": true,
"input_ids": ["rtp-in-1"],
"output_ids": ["rtp-out-1", "srt-out-1"]
}
}

input_ids is normally a single-element array; it carries multiple IDs for redundancy (SMPTE 2022-7 hitless) and switcher flows. output_ids lists every output the flow fans out to. The engine dereferences these into a ResolvedFlow (pulling each InputDefinition / OutputConfig from the top-level entity tables) before starting the flow.

Error responses:

Status Condition
404 Flow 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. The input_ids and output_ids must reference inputs/outputs that already exist as top-level entities — create them first via POST /api/v1/inputs and POST /api/v1/outputs.

Auth: Requires admin role.

Request body:

{
"id": "new-flow",
"name": "New Feed",
"enabled": true,
"input_ids": ["rtp-in-1"],
"output_ids": ["out-1"]
}

Response (200):

Returns the created flow configuration in the standard envelope.

Error responses:

Status Condition
400 Validation failure (invalid addresses, empty ID/name, bad FEC params)
409 A flow with the same ID already exists
500 Failed 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_ids": ["rtp-in-1"],
"output_ids": ["out-1"]
}'

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:

Parameter Type Description
flow_id string Unique 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:

Status Condition
400 Validation failure
404 Flow not found
500 Failed 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_ids": ["rtp-in-1"],
"output_ids": ["out-1"]
}'

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

Auth: Requires admin role.

Path parameters:

Parameter Type Description
flow_id string Unique identifier of the flow to delete

Response (200):

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

Error responses:

Status Condition
404 Flow not found
500 Failed 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:

Parameter Type Description
flow_id string Unique identifier of the flow to start

Response (200):

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

Error responses:

Status Condition
404 Flow not found in configuration
409 Flow is already running
500 Engine 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:

Parameter Type Description
flow_id string Unique identifier of the flow to stop

Response (200):

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

Error responses:

Status Condition
404 Flow not found in configuration
409 Flow is not currently running
500 Engine 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:

Parameter Type Description
flow_id string Unique identifier of the flow to restart

Response (200):

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

Error responses:

Status Condition
404 Flow not found in configuration
500 Engine 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"

POST /api/v1/flows/{flow_id}/activate-input

Section titled “POST /api/v1/flows/{flow_id}/activate-input”

Switch the live input of a multi-input flow (switcher / redundancy). Selects which of the flow’s input_ids is on-air without restarting the flow.

Auth: Requires admin role.

Request body:

{
"input_id": "backup-srt-in"
}

Error responses:

Status Condition
400 The named input is not one of the flow’s input_ids
404 Flow not found
409 Flow is not running

Hot-swap the PID-bus assembly plan of an assembled (PID-bus / MPTS) flow in place, without tearing the flow down. Used to re-program the ES-to-program mapping of a running assembled flow.

Auth: Requires admin role.

Request body: A flow-assembly plan object (see the Configuration Guide).

Error responses:

Status Condition
400 Assembly plan validation failure
404 Flow not found
409 Flow is not an assembled flow

Like inputs, outputs are top-level, first-class entities; a flow references them by ID via its output_ids array. The body is an OutputConfig whose type field selects the variant — a free-form string: udp, rtp, rtp_audio, srt, rist, rtmp, webrtc, hls, cmaf, bonded, display, st2110_20, st2110_23, st2110_30, st2110_31, st2110_40 (plus mxl_video / mxl_audio / mxl_anc under the mxl feature, and sdi under sdi-decklink). See the Configuration Guide for per-type fields.

Two types an operator often looks for are not on that list. WHIP is not an output type — to push to a WHIP endpoint use type: "webrtc" with mode: "whip_client" and a whip_url (mode: "whep_server" is the other half of that variant). RTSP is ingress-only and has no output form at all. Both are rejected at deserialization, since type selects an enum variant.

List all configured outputs.

Auth: Requires valid JWT (any role).

Create a new top-level output. Validated and persisted.

Auth: Requires admin role.

Error responses:

Status Condition
400 Output validation failure
409 An output with the same ID already exists
500 Failed to persist config to disk

Retrieve a single output definition. Returns 404 if not found.

Auth: Requires valid JWT (any role).

Replace an output definition.

Auth: Requires admin role.

Delete an output. Fails if the output is still referenced by a flow.

Auth: Requires admin role.

Error responses:

Status Condition
404 Output not found
409 Output is still referenced by one or more flows
500 Failed to persist config to disk

Toggle an output on or off at runtime without removing it from the flow. A disabled output stops emitting but stays configured.

Auth: Requires admin role.

Request body:

{
"active": false
}

Error responses:

Status Condition
404 Output not found

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:

Parameter Type Description
flow_id string Flow 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:

Status Condition
400 Output validation failure
404 Flow not found
409 An output with the same ID already exists in the flow
500 Failed 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:

Parameter Type Description
flow_id string Flow containing the output
output_id string Output to remove

Response (200):

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

Error responses:

Status Condition
404 Flow not found, or output not found within the flow
500 Failed 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"

Read and update the node’s PTP (Precision Time Protocol) settings, used by the master-clock discipline for ST 2110 / MXL and PTP-locked flows. The edge reports the live lock/offset view separately via FlowStats.ptp_state.

Retrieve the current PTP configuration and status.

Auth: Requires valid JWT (any role).

Update the PTP configuration (e.g. clock domain, interface). The settings are persisted to the file bilbycast-ptp-helper polls at 1 Hz, so a new mode is picked up within about a second.

Auth: Requires valid JWT (any role) when auth is enabled — the handler carries no role check, so a monitor token can write PTP settings.

Request body:

{
"mode": "slave-only",
"iface": "eno1",
"domain": 127,
"offset_warn_ns": 100000
}
Field Type Required Description
mode string Yes auto, grandmaster, slave-only or off. Case-insensitive; gm / master alias grandmaster, slave aliases slave-only, and disabled / none alias off. Any other value returns 400
iface string No Interface ptp4l binds. 1–15 bytes, alphanumeric plus ._- (Linux IFNAMSIZ); empty means unset
domain integer No IEEE 1588 domain, 0–127
priority1 integer No Grandmaster priority1. Ignored in slave-only, which is pinned to 255 before the settings are persisted
scan_timeout integer No Seconds the auto mode scans for an existing grandmaster, 1–60
offset_warn_ns integer No Master-offset warning threshold in ns, 0–1000000000. 0 is treated as disabled
path_delay_warn_ns integer No Mean-path-delay warning threshold in ns, same range and 0 handling

The response echoes the normalised settings plus config_path, the file the helper reads.


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:

Field Type Description
uptime_secs integer Application uptime in seconds
total_flows integer Total configured flows
active_flows integer Currently running flows
version string Application version

Per-flow fields:

Field Type Description
flow_id string Flow identifier
flow_name string Display name
state string "Idle", "Starting", "Running", "Error", or "Stopped"
health string "Healthy", "Warning", "Error", or "Critical" (RP 2129 M6)
uptime_secs integer Seconds since the flow was started
input object Input leg statistics (see below)
outputs array Per-output statistics (see below)
tr101290 object/null TR-101290 analysis (present when running)
media_analysis object/null Media content analysis — codec, resolution, frame rate, audio format, per-PID bitrate (present when running and media_analysis config is true)
iat object/null Inter-arrival time stats in microseconds
pdv_jitter_us float/null Packet delivery variation (jitter) in microseconds
bandwidth_exceeded boolean true if the flow’s input bitrate currently exceeds the configured bandwidth_limit. Omitted when false.
bandwidth_blocked boolean true if the flow is currently gated (packets dropped) due to bandwidth limit enforcement. Omitted when false.
bandwidth_limit_mbps float/null Configured bandwidth limit in Mbps (for display). Absent if no limit configured.
pcr_trust_flow object/absent Flow-wide PCR-accuracy rollup — percentiles over the union of every output’s PCR trust reservoir, so a multi-output flow still has one summary number. This is what broadcast quality gate 4 reads. Absent until an output has collected enough samples
master_clock object/absent Master-clock telemetry: which clock the flow is paced against, whether it is locked, and recovered jitter / rate offset for PLL masters. Every running flow has one — see Clocking
av_skew object/absent Edge-added A/V skew — the lip-sync error this edge’s PTS-touching stages introduce, measured on the active input’s path
av_interleave_flow object/absent Flow-wide A/V mux-interleave rollup (worst-case p95 across outputs). This is interleave, not lip-sync — av_skew is the lip-sync number
content_analysis object/absent In-depth content-analysis snapshot; present when the flow enables content_analysis at the lite, audio_full or video_full tier. Each sub-field is independently optional, so a Lite-only selection returns a minimal payload
recording object/absent Replay-server recording counters (armed, current_pts_90khz, segments_written, bytes_written, packets_dropped, segments_pruned, index_entries). Present only with a recording block configured and the replay feature compiled in — see Replay
health_reasons array Why health is what it is: one entry per triggered condition, most-severe first, each with a stable code, its own severity and an operator-facing detail. health equals the maximum severity across these. Omitted when empty (a healthy flow)
assembly_health object/absent Per-slot liveness rollup for assembled (PID-bus) flows: total slot count plus the subset currently stalled. Absent on passthrough flows — see Flow Assembly
per_es array/absent Per-elementary-stream counters off the PID bus, one entry per (input_id, source_pid); entries also carry out_pid while an assembler runs. Populated only for flows with an active assembly — passthrough flows use media_analysis.program_bitrates instead
inputs_live array/absent Per-input liveness snapshot, one entry per configured input including passive / non-switched ones, so a UI can render “NO SIGNAL” per input rather than per flow
active_input_id string/absent Which input is currently publishing to the broadcast channel. Absent when the flow has no inputs or is idle
ptp_state object/absent PTP clock state. Populated by ST 2110 flows whose clock_domain is set; absent otherwise
network_legs object/absent Per-leg counters for SMPTE 2022-7 Red/Blue dual-network operation. Present only when the flow’s input sets redundancy
essence_flows array/absent Per-essence breakdown when the flow is part of a multi-essence ST 2110 flow group
thumbnail object/absent Thumbnail-generation statistics

Input stats fields:

Field Type Description
input_type string Input variant — a free-form string such as rtp, udp, rtp_audio, srt, rist, rtmp, rtsp, webrtc, whep, bonded, media_player, test_pattern, replay, or st2110_20 / st2110_23 / st2110_30 / st2110_31 / st2110_40 (plus mosaic / mxl_* / sdi under their features)
state string Connection state (e.g., "receiving", "connecting")
packets_received integer Total RTP packets received
bytes_received integer Total bytes received
bitrate_bps integer Current bitrate in bits/sec
packets_lost integer Packets lost (sequence gaps)
packets_filtered integer Packets dropped by ingress filters
packets_recovered_fec integer Packets recovered via FEC
srt_stats object/null SRT leg 1 stats (if SRT input)
srt_leg2_stats object/null SRT leg 2 stats (if redundancy enabled)
redundancy_switches integer SMPTE 2022-7 leg switch count

Output stats fields:

Field Type Description
output_id string Output identifier
output_name string Display name
output_type string Output variant — a free-form string such as udp, rtp, rtp_audio, srt, rist, rtmp, webrtc, hls, cmaf, bonded, display, or st2110_20 / st2110_23 / st2110_30 / st2110_31 / st2110_40 (plus mxl_* / sdi under their features). A WHIP push reports as webrtc; there is no rtsp or whip output
state string Connection state
packets_sent integer Total packets sent
bytes_sent integer Total bytes sent
bitrate_bps integer Current bitrate in bits/sec
packets_dropped integer Packets dropped (channel full)
fec_packets_sent integer FEC packets sent
wire_pacing_tier string Active wire-pacing release tier — "so_txtime", "clock_nanosleep_fifo", or "clock_nanosleep". Set once at output startup. Default is "clock_nanosleep_fifo"; the SO_TXTIME tier activates only when BILBYCAST_ENABLE_TXTIME=1 is set on the edge process AND the kernel accepts the setsockopt (requires CAP_NET_ADMIN). See Wire-Time Precision. Absent on outputs that don’t own a UDP socket directly (SRT, RIST, RTMP, HLS, CMAF, WebRTC).
wire_pacing_late integer Datagrams the kernel rejected as late on the SO_TXTIME path (target tx time landed in the past — EOVERFLOW). Always 0 on userspace-sleep release paths (no errqueue).
srt_stats object/null SRT leg 1 stats (if SRT output)
srt_leg2_stats object/null SRT leg 2 stats (if redundancy)

SRT leg stats fields:

Field Type Description
state string SRT socket state (e.g., "connected", "broken")
rtt_ms float Round-trip time in milliseconds
send_rate_mbps float Estimated send rate in Mbps
recv_rate_mbps float Estimated receive rate in Mbps
pkt_loss_total integer Total packets lost
pkt_retransmit_total integer Total retransmitted packets
uptime_ms integer Socket 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:

Parameter Type Description
flow_id string Flow to retrieve stats for

Response (200):

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

Error responses:

Status Condition
404 Flow 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": 2,
"server": {
"listen_addrs": ["127.0.0.1", "[::1]"],
"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:

Status Condition
400 Config validation failure (duplicate flow IDs, invalid addresses, etc.)
500 Failed 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:

Status Condition
400 Loaded config fails validation
500 Config 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:

Metric Type Description
bilbycast_edge_info{version="..."} gauge Application version (always 1)
bilbycast_edge_uptime_seconds gauge Seconds since startup
bilbycast_edge_flows_total gauge Total configured flows
bilbycast_edge_flows_active gauge Currently running flows

Per-flow input metrics (labeled by flow_id):

Metric Type Description
bilbycast_edge_flow_input_packets_total counter RTP packets received
bilbycast_edge_flow_input_bytes_total counter Bytes received
bilbycast_edge_flow_input_bitrate_bps gauge Input bitrate (bits/sec)
bilbycast_edge_flow_input_packets_lost counter Packets lost
bilbycast_edge_flow_input_fec_recovered_total counter Packets recovered via FEC
bilbycast_edge_flow_input_redundancy_switches_total counter SMPTE 2022-7 leg switches
bilbycast_edge_flow_input_packets_filtered counter Packets dropped by ingress filters
bilbycast_edge_flow_pdv_jitter_us gauge PDV jitter in microseconds
bilbycast_edge_flow_iat_avg_us gauge Average inter-arrival time in microseconds

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

Metric Type Description
bilbycast_edge_flow_output_packets_total counter Packets sent
bilbycast_edge_flow_output_bytes_total counter Bytes sent
bilbycast_edge_flow_output_bitrate_bps gauge Output bitrate (bits/sec)
bilbycast_edge_flow_output_packets_dropped counter Packets dropped
bilbycast_edge_flow_output_fec_sent_total counter FEC packets sent
bilbycast_edge_flow_output_latency_us gauge End-to-end output latency in microseconds. Carries an extra stat label — min, avg or max — and is emitted only for outputs that report latency
bilbycast_edge_flow_output_latency_frames gauge The same latency expressed in video frames, when a frame rate is known

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

Metric Type Description
bilbycast_edge_srt_rtt_ms gauge SRT round-trip time in ms
bilbycast_edge_srt_loss_total counter SRT total packet loss

RIST metrics (labeled by flow_id, leg_role = input or output, output_id on output legs, and leg = leg1 or leg2):

Metric Type Description
bilbycast_edge_rist_rtt_ms gauge RIST round-trip time in ms
bilbycast_edge_rist_nack_sent_total counter NACK messages sent by the receiver
bilbycast_edge_rist_nack_received_total counter NACK messages received by the sender
bilbycast_edge_rist_retransmit_total counter Packets retransmitted by the sender
bilbycast_edge_rist_packets_lost_total counter Packets not recovered by ARQ
bilbycast_edge_rist_packets_recovered_total counter Packets recovered via retransmit

Bond metrics. The per-bond series are labeled by flow_id, leg_role (input or output) and output_id on output legs; the per-path series add path_id, path_name and transport. See Bonding.

Metric Type Description
bilbycast_edge_bond_gaps_recovered counter Sequence gaps recovered by ARQ
bilbycast_edge_bond_gaps_lost counter Sequence gaps not recovered
bilbycast_edge_bond_packets_duplicated counter Packets duplicated across paths (sender side)
bilbycast_edge_bond_throughput_bps gauge Aggregate bond bandwidth in bits/sec (sum of per-path)
bilbycast_edge_bond_rtt_ms gauge Per-path round-trip time in ms
bilbycast_edge_bond_loss_fraction gauge Per-path loss rate, 0.0–1.0
bilbycast_edge_bond_path_throughput_bps gauge Per-path bandwidth in bits/sec
bilbycast_edge_bond_path_packets_sent counter Packets sent on the path
bilbycast_edge_bond_path_packets_received counter Packets received on the path
bilbycast_edge_bond_path_retransmits_sent counter Retransmits emitted on the path (sender side)
bilbycast_edge_bond_path_nacks_sent counter NACKs sent on the path (receiver side)
bilbycast_edge_bond_path_nacks_received counter NACKs received on the path (sender side)
bilbycast_edge_bond_path_keepalives_sent counter Keepalives sent on the path
bilbycast_edge_bond_path_dead gauge Path liveness — 1 when dead, 0 when alive (there is no path_alive)

TR-101290 metrics (labeled by flow_id):

Metric Type Description
bilbycast_edge_tr101290_ts_packets_total counter TS packets analyzed
bilbycast_edge_tr101290_sync_byte_errors_total counter Sync byte errors
bilbycast_edge_tr101290_cc_errors_total counter Continuity counter errors
bilbycast_edge_tr101290_pat_errors_total counter PAT timeout errors
bilbycast_edge_tr101290_pmt_errors_total counter PMT timeout errors
bilbycast_edge_tr101290_tei_errors_total counter Transport error indicator errors
bilbycast_edge_tr101290_pcr_discontinuity_errors_total counter PCR discontinuity errors
bilbycast_edge_tr101290_pcr_accuracy_errors_total counter PCR accuracy errors
bilbycast_edge_tr101290_pid_errors_total counter PID errors (ES PIDs missing)
bilbycast_edge_tr101290_crc_errors_total counter CRC-32 errors on PAT/PMT sections

Media analysis metrics (labeled by flow_id and pid):

Metric Type Description
bilbycast_edge_media_video_info info Video stream info (labels: codec, resolution, profile, level)
bilbycast_edge_media_video_framerate gauge Video frame rate in fps
bilbycast_edge_media_audio_info info Audio stream info (labels: codec, sample_rate, channels, language)
bilbycast_edge_media_pid_bitrate_bps gauge Per-PID bitrate in bits/sec (label: type = video or audio)
bilbycast_edge_media_total_bitrate_bps gauge Total TS bitrate in bits/sec

Only metrics for currently running flows are emitted.

The families below are node-level rather than per-flow, so they are emitted whether or not a flow is running.

PTP metrics (labeled by domain):

Metric Type Description
bilbycast_edge_ptp_locked gauge 1 when the clock is locked or acting as master, 0 otherwise
bilbycast_edge_ptp_state gauge Lock state as a label — the active state label (unavailable, acquiring, locked, holdover, master, unknown) carries value 1
bilbycast_edge_ptp_offset_ns gauge Offset from master in nanoseconds. Emitted only while slaved (locked or holdover)
bilbycast_edge_ptp_mean_path_delay_ns gauge Mean path delay in nanoseconds. Slaved only
bilbycast_edge_ptp_steps_removed gauge Steps removed from the grandmaster. Slaved only

Replay metrics (no labels; present only on a binary built with the replay feature):

Metric Type Description
bilbycast_edge_replay_recordings_count gauge On-disk replay recordings
bilbycast_edge_replay_recordings_bytes gauge Bytes consumed by on-disk replay segments
bilbycast_edge_replay_orphan_recordings_count gauge Recordings with no flow currently armed against them
bilbycast_edge_replay_orphan_bytes gauge Bytes consumed by orphan recordings — watch this for storage creep
bilbycast_edge_replay_root_free_bytes gauge Free bytes on the replay-root filesystem. Omitted when the filesystem cannot be stat’d
bilbycast_edge_replay_root_total_bytes gauge Total bytes on the replay-root filesystem. Same omission rule

Host resource metrics (no labels):

Metric Type Description
bilbycast_edge_system_cpu_percent gauge System CPU usage percentage
bilbycast_edge_system_ram_percent gauge System RAM usage percentage
bilbycast_edge_system_ram_used_bytes gauge System RAM used in bytes
bilbycast_edge_system_ram_total_bytes gauge System RAM total in bytes
bilbycast_edge_system_resources_critical gauge 1 when the node is in a critical resource state. Note the plural resources

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_addrs": [
"relay-primary.example.com:4433",
"relay-backup.example.com:4433"
],
"active_relay_idx": 0,
"active_relay_addr": "relay-primary.example.com:4433",
"stats": { "packets_sent": 0, "packets_received": 0, "bytes_sent": 0, "bytes_received": 0 }
}
]
}

active_relay_idx and active_relay_addr only appear for running relay-mode tunnels and reflect which relay is currently carrying traffic (0 = primary). When the primary is unreachable and failover occurs, these flip to 1 / the backup address.

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_addrs": [
"relay-primary.example.com:4433",
"relay-backup.example.com:4433"
],
"active_relay_idx": 1,
"active_relay_addr": "relay-backup.example.com:4433",
"stats": { "packets_sent": 0, "packets_received": 0, "bytes_sent": 0, "bytes_received": 0 }
}

Response (404 Not Found):

{
"error": "Tunnel not found"
}

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

Auth: Requires valid JWT (any role) when auth is enabled — the handler carries no role check, so a monitor token can create a tunnel. The result is persisted to config.json (secrets to secrets.json), so it survives a restart.

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_addrs": [
"relay-primary.example.com:4433",
"relay-backup.example.com:4433"
],
"tunnel_encryption_key": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}

relay_addrs is an ordered list (primary first, optional backup second). A single-relay tunnel uses a one-element list. The legacy single-field "relay_addr": "host:port" form is still accepted and migrated automatically.

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 (any role) when auth is enabled — the handler carries no role check, so a monitor token can tear a live tunnel down. The removal is reconciled out of config.json, so it is not resurrected on the next restart.

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 must be sent as an Authorization: Bearer header on the upgrade request — there is no query-parameter fallback. A browser WebSocket cannot set request headers, so reaching this endpoint from a page needs either a reverse proxy that injects the header or an auth-disabled node. See Auth Configuration.

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 (sends no credential, so it works only against an auth-disabled node or through a header-injecting proxy — see Auth above):

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 Status Error Type Description
400 Bad Request Request body failed validation, malformed JSON, unsupported grant type
401 Unauthorized Missing or invalid Authorization header, expired token, bad signature
403 Forbidden Valid token but insufficient role (admin required)
404 Not Found Flow or output does not exist
409 Conflict Resource already exists, flow already running/stopped
500 Internal Server Error Disk 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"
}

Method Path Auth Role Description
GET /health No - Health check
POST /oauth/token No - Get JWT token
GET /metrics Configurable any Prometheus metrics
GET /api/v1/inputs Yes any List all inputs
POST /api/v1/inputs Yes admin Create input
GET /api/v1/inputs/{input_id} Yes any Get input details
PUT /api/v1/inputs/{input_id} Yes admin Update input
DELETE /api/v1/inputs/{input_id} Yes admin Delete input
GET /api/v1/outputs Yes any List all outputs
POST /api/v1/outputs Yes admin Create output
GET /api/v1/outputs/{output_id} Yes any Get output details
PUT /api/v1/outputs/{output_id} Yes admin Update output
DELETE /api/v1/outputs/{output_id} Yes admin Delete output
POST /api/v1/outputs/{output_id}/active Yes admin Enable/disable output at runtime
GET /api/v1/flows Yes any List all flows
GET /api/v1/flows/{flow_id} Yes any Get flow details
POST /api/v1/flows Yes admin Create flow
PUT /api/v1/flows/{flow_id} Yes admin Update flow
DELETE /api/v1/flows/{flow_id} Yes admin Delete flow
POST /api/v1/flows/{flow_id}/start Yes admin Start flow
POST /api/v1/flows/{flow_id}/stop Yes admin Stop flow
POST /api/v1/flows/{flow_id}/restart Yes admin Restart flow
POST /api/v1/flows/{flow_id}/activate-input Yes admin Switch live input (switcher/redundancy)
PUT /api/v1/flows/{flow_id}/assembly Yes admin Hot-swap PID-bus assembly plan
POST /api/v1/flows/{flow_id}/outputs Yes admin Add output to flow
DELETE /api/v1/flows/{flow_id}/outputs/{output_id} Yes admin Remove output from flow
POST /api/v1/flows/{flow_id}/whip Yes any WHIP: Accept WebRTC publisher (SDP offer → answer)
DELETE /api/v1/flows/{flow_id}/whip/{session_id} Yes any WHIP: Disconnect publisher
POST /api/v1/flows/{flow_id}/whep Yes any WHEP: Accept WebRTC viewer (SDP offer → answer)
DELETE /api/v1/flows/{flow_id}/whep/{session_id} Yes any WHEP: Disconnect viewer
GET /api/v1/tunnels Yes any List all tunnels
GET /api/v1/tunnels/{id} Yes any Get tunnel status
POST /api/v1/tunnels Yes any Create tunnel
DELETE /api/v1/tunnels/{id} Yes any Delete tunnel
GET /api/v1/stats Yes any All statistics
GET /api/v1/stats/{flow_id} Yes any Single flow stats
GET /api/v1/config Yes any Get running config
PUT /api/v1/config Yes admin Replace entire config
POST /api/v1/config/reload Yes admin Reload config from disk
GET /api/v1/ptp Yes any Get PTP configuration + status
PUT /api/v1/ptp Yes any Update PTP configuration
GET /api/v1/ws/stats Yes any WebSocket stats stream
GET /x-nmos/node/v1.3/ No - NMOS IS-04: Node API root
GET /x-nmos/node/v1.3/self No - 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}/staged No - NMOS IS-05: Get staged params
PATCH /x-nmos/connection/v1.1/single/senders/{id}/staged No - NMOS IS-05: Update staged + activate
GET /x-nmos/connection/v1.1/single/senders/{id}/active No - NMOS IS-05: Get active params
GET /x-nmos/connection/v1.1/single/senders/{id}/transporttype No - NMOS IS-05: Get transport type
GET /x-nmos/connection/v1.1/single/senders/{id}/constraints No - 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}/staged No - NMOS IS-05: Get staged params
PATCH /x-nmos/connection/v1.1/single/receivers/{id}/staged No - NMOS IS-05: Update staged + activate
GET /x-nmos/connection/v1.1/single/receivers/{id}/active No - NMOS IS-05: Get active params
GET /x-nmos/connection/v1.1/single/receivers/{id}/transporttype No - NMOS IS-05: Get transport type
GET /x-nmos/connection/v1.1/single/receivers/{id}/constraints No - NMOS IS-05: Get constraints
GET /x-nmos/channelmapping/v1.0/ No - NMOS IS-08: Channel mapping root
GET /x-nmos/channelmapping/v1.0/io No - NMOS IS-08: List inputs/outputs (ST 2110-30/-31 audio)
GET /x-nmos/channelmapping/v1.0/map/active No - NMOS IS-08: Active channel map (persisted to disk)
GET /x-nmos/channelmapping/v1.0/map/staged No - NMOS IS-08: Staged channel map (in-memory)
POST /x-nmos/channelmapping/v1.0/map/staged No - NMOS IS-08: Stage a new channel map (1024 outputs × 64 ch limit)
POST /x-nmos/channelmapping/v1.0/map/activate No - NMOS IS-08: Activate the staged map and persist

The four WHIP/WHEP rows exist only on a binary built with the default-on webrtc Cargo feature; on a --no-default-features build the routes are not registered at all. They sit behind the same JWT middleware as every other protected route but enforce no role of their own. The only per-endpoint gate is the optional per-flow bearer_token on the WHIP input / WHEP output config, and it covers the two POST signalling routes alone: when it is set, the offer handler compares it against the request’s Authorization: Bearer value and returns 401 on a mismatch; when it is unset there is no per-flow gate, so any valid node JWT — or any caller at all on an auth-disabled node — can open a session. The two DELETE teardown routes check no bearer token in any configuration.

ST 2110-30/-31 inputs are reported as urn:x-nmos:format:audio sources/flows/receivers; ST 2110-40 inputs are reported as urn:x-nmos:format:data. Older mux flows continue to report urn:x-nmos:format:mux so existing NMOS controllers see no behavioural change.

Audio receivers (InputConfig::St2110_30 / St2110_31) include a caps block with a media_types list and a constraint_sets list keyed by urn:x-nmos:cap:format:* URNs:

{
"caps": {
"media_types": ["audio/L16", "audio/L24"],
"constraint_sets": [{
"urn:x-nmos:cap:format:media_type": { "enum": ["audio/L16", "audio/L24"] },
"urn:x-nmos:cap:format:sample_rate": { "enum": [{ "numerator": 48000 }] },
"urn:x-nmos:cap:format:channel_count": { "enum": [2] },
"urn:x-nmos:cap:format:sample_depth": { "enum": [24] }
}]
}
}

ST 2110-40 receivers advertise media_types: ["video/smpte291"]. Non-ST-2110 receivers continue to advertise the historical video/MP2T shape.

When any flow on the node sets clock_domain, the IS-04 /self resource includes a single PTP clock entry (name: "clk0", ref_type: "ptp", version: "IEEE1588-2008"). Sources whose flow has clock_domain set reference this clock by name. The locked field is reported as false until live PTP integration lands; FlowStats.ptp_state.lock_state carries the real view.

The IS-08 endpoints expose every ST 2110-30/-31 audio input and output under /io. The active map is persisted to <config_dir>/nmos_channel_map.json (next to config.json) and reloaded on startup. The endpoints support the standard GET/POST-staged + POST-activate workflow (there is no PUT route). Bilbycast does not currently re-route channels internally — the map is a passthrough — but the endpoints exist so external NMOS controllers can stage and activate maps.

Bounds: at most 1024 outputs per map, at most 64 channels per output. A controller exceeding these limits receives a 413 PAYLOAD_TOO_LARGE response.

On startup the edge registers _nmos-node._tcp.local. via the mdns-sd crate. Failures (no multicast on the selected interface, daemon errors) are logged once and swallowed; flow startup is never blocked.