Skip to content

API Reference

bilbycast-relay exposes a small REST API on port 4480 (default) for health checks, Prometheus scraping, and tunnel introspection. It’s read-mostly: the introspection endpoints are read-only, and normal operational changes (authorize_tunnel, revoke_tunnel, disconnect_edge) flow through the manager’s WebSocket protocol rather than REST. The only mutating REST routes are two fail-closed administrative teardown endpoints — DELETE /api/v1/tunnels/{id} and DELETE /api/v1/udp-sessions/{id} — escape hatches for tearing down an orphaned tunnel or native-UDP session when the manager (the normal cleanup path) is unavailable. Both refuse with 403 unless api_token is configured and presented.

Set api_token in relay.json to require Bearer auth. The token must be 32–128 characters:

{
"quic_addr": "0.0.0.0:4433",
"api_addr": "0.0.0.0:4480",
"api_token": "<32-128 chars>"
}

When api_token is configured, every endpoint except /health requires:

Authorization: Bearer <token>

When unset, every endpoint is open. The relay does constant-time comparison so an invalid token can’t be discovered by timing the 401 response.

Always reachable, even when api_token is set. Used by load balancers, the manager’s reachability probe, and your own monitoring.

Terminal window
curl http://relay-host:4480/health
{
"status": "ok",
"version": "0.x.y",
"uptime_secs": 12345,
"connected_edges": 4,
"total_tunnels": 7,
"active_tunnels": 5
}

A tunnel is active when both legs (ingress + egress) are bound. Total counts include tunnels in the half-bound TunnelWaiting state.

Prometheus exposition format. Auth-gated when api_token is set.

Terminal window
curl -H "Authorization: Bearer <token>" \
http://relay-host:4480/metrics

Useful series:

  • bilbycast_relay_uptime_seconds
  • bilbycast_relay_connected_edges
  • bilbycast_relay_active_tunnels / bilbycast_relay_total_tunnels
  • bilbycast_relay_bytes_ingress_total / bilbycast_relay_bytes_egress_total
  • bilbycast_relay_bandwidth_bps
  • bilbycast_relay_peak_tunnels / bilbycast_relay_peak_edges

Pair with the Stats reference for the full series catalogue.

Aggregate relay-wide stats. Auth-gated.

Terminal window
curl -H "Authorization: Bearer <token>" \
http://relay-host:4480/api/v1/stats
{
"uptime_secs": 12345,
"connected_edges": 4,
"total_tunnels": 7,
"active_tunnels": 5,
"total_bytes_ingress": 1234567890,
"total_bytes_egress": 1234567890,
"total_bytes_forwarded": 2469135780,
"total_bandwidth_bps": 50000000,
"total_tcp_streams": 14,
"active_tcp_streams": 6,
"total_udp_datagrams": 9876543,
"peak_tunnels": 12,
"peak_edges": 9,
"connections_total": 87
}

total_bandwidth_bps is computed across the most recent reporting window — it’s the live throughput, not a lifetime average.

Per-tunnel state. Auth-gated.

Terminal window
curl -H "Authorization: Bearer <token>" \
http://relay-host:4480/api/v1/tunnels
{
"tunnels": [
{
"tunnel_id": "550e8400-e29b-41d4-a716-446655440000",
"state": "active",
"ingress_edge_id": "edge-syd-1",
"egress_edge_id": "edge-perth-1",
"bytes_ingress": 1234567,
"bytes_egress": 1234567,
"tcp_streams_total": 4,
"tcp_streams_active": 2,
"udp_datagrams_total": 5678
}
]
}

state is one of waiting (only one leg bound), active (both legs bound), or unbinding (transition state).

Connected edge nodes. Auth-gated.

Terminal window
curl -H "Authorization: Bearer <token>" \
http://relay-host:4480/api/v1/edges
{
"edges": [
{
"edge_id": "edge-syd-1",
"remote_addr": "203.0.113.10:53219"
}
]
}

edge_id is whatever the edge sent in its optional Identify { edge_id } message — typically the manager node_id so the manager can correlate topology. If the edge never sent Identify, the relay synthesises a connection_id from the remote address plus a counter.

Per-session state for the native plain-UDP data plane (native SRT/RIST over relay and individual bond legs carried on the :4434 carrier). Auth-gated.

Terminal window
curl -H "Authorization: Bearer <token>" \
http://relay-host:4480/api/v1/udp-sessions
{
"sessions": [
{
"tunnel_id": "550e8400-e29b-41d4-a716-446655440000",
"transport": "udp",
"status": "active",
"ingress_addr": "203.0.113.10:53219",
"egress_addr": "198.51.100.7:41888",
"bytes_ingress": 1234567,
"bytes_egress": 1234567,
"datagrams": 5678,
"uptime_secs": 321
}
]
}

A session is active once both edges’ post-NAT source addresses have been latched by source-address rendezvous; before that it’s waiting. ingress_addr / egress_addr are null until each side latches.

DELETE /api/v1/tunnels/{id} (admin, fail-closed)

Section titled “DELETE /api/v1/tunnels/{id} (admin, fail-closed)”

Force-tears-down a QUIC tunnel: revokes its bind authorisation and removes the live tunnel entry, notifying any still-bound peer with TunnelDown. This is an administrative escape hatch for orphaned tunnels — the normal teardown path is the manager’s close_tunnel WebSocket command.

Fail-closed: this route returns 403 unless api_token is configured, regardless of whether a token is presented (a destructive route must never be reachable unauthenticated). Invalid UUID → 400; unknown tunnel → 404.

Terminal window
curl -X DELETE -H "Authorization: Bearer <token>" \
http://relay-host:4480/api/v1/tunnels/550e8400-e29b-41d4-a716-446655440000

DELETE /api/v1/udp-sessions/{id} (admin, fail-closed)

Section titled “DELETE /api/v1/udp-sessions/{id} (admin, fail-closed)”

The native-UDP counterpart to the tunnel DELETE — removes an orphaned plain-UDP session by tunnel UUID. Same fail-closed semantics: 403 without a configured api_token, 400 on an invalid UUID, 404 on an unknown session.

Terminal window
curl -X DELETE -H "Authorization: Bearer <token>" \
http://relay-host:4480/api/v1/udp-sessions/550e8400-e29b-41d4-a716-446655440000

Per-tunnel HMAC-SHA256 bind authentication is managed by bilbycast-manager through its WebSocket protocol — not via REST. The manager pre-registers expected ingress + egress bind tokens on the relay using authorize_tunnel, and revokes them with revoke_tunnel. When relay.json sets require_bind_auth: true, every TunnelBind from an edge must include a matching bind_token or the relay rejects the bind with TunnelDown { reason: "bind authentication failed" }.

This is the mechanism the manager uses to ensure that only the two edges it pairs end up sharing a tunnel. The relay holds no operator-supplied state for this — it just maintains a DashMap of pre-registered tokens that the manager refreshes.

For the operator-visible side of this — how the manager authors tunnels and which UI surfaces the per-tunnel auth state — see IP tunneling.

  • Bearer tokens: 32–128 characters.
  • QUIC control messages: max 1 MB per message.
  • Per-connection: 1024 bidirectional QUIC streams, 256 unidirectional, 5 s keep-alive.
  • UDP datagram buffers: 2 MB send + 2 MB receive (sized for SRT at 10 Mbps in flight).