Quikcast Help

Quikcast native management API

The unversioned /api surface runs on admin_listen, which must bind loopback. Forward it through a private proxy when remote administration is needed. Continuous audio, source authentication, metadata updates, public HLS, HLS ingest, health, and metrics retain their existing independent routes and credentials.

Configuration and authentication

trusted_proxies = ["127.0.0.1/32", "10.20.0.0/16"] # optional; top-level [management] token_file = "secrets/management-token" # Alternatively: token = "a-generated-machine-secret" [[mounts]] path = "/radio.mp3" username = "source" secret_file = "secrets/radio-source" listener_limit = 500

Configure exactly one token or token_file. Relative secret-file paths resolve against the configuration directory. Generate a dedicated token, for example:

mkdir -p config/secrets umask 077 openssl rand -hex 32 > config/secrets/management-token

The token must contain 32–512 printable ASCII bytes without spaces. Use a generated high-entropy secret; length validation cannot establish entropy. A secret file may end in LF or CRLF. Loading is synchronous, bounded, and fail-fast before binding. Secrets have no debug representation and never appear in responses or logs. Do not reuse a source or HLS producer secret as a management token.

Every endpoint below requires Authorization: Bearer <token>; the scheme is case-insensitive. Missing, incorrect, malformed, or oversized bearer credentials receive 401 unauthorized, with a Bearer challenge. Comparison uses fixed-size padded arrays and constant-time length/content comparison. At most 519 authorization bytes are accepted. The request parser additionally caps all headers at 16 KiB and 64 fields; duplicate Authorization fields or invalid HTTP framing are rejected by the transport parser. Transport failures before routing remain the existing finite HTTP errors (400, 408, 417, or 431), rather than API JSON.

If [management] is omitted, /api fails closed with 401 for every request. Health and metrics remain lightweight, loopback-only operational endpoints without the management token. Listener and source routes never require this token.

Examples assume a securely supplied environment variable:

curl -H "Authorization: Bearer $QUIKCAST_TOKEN" http://127.0.0.1:9090/api/server

All management requests have no body. Omit Content-Length or send Content-Length: 0. Nonempty/chunked bodies and Expect requests receive 400 invalid_request. Queries reject unknown and duplicate parameters. JSON responses use application/json and Cache-Control: no-store. Connections close after each response. Successful DELETE returns 204 with no body. Unsupported methods receive 405 method_not_allowed and an Allow header.

Using the examples

Set the administration URL and load the token from your management token file. Replace the path with the actual token_file path from your configuration:

export QUIKCAST_ADMIN_URL='http://127.0.0.1:9090' export QUIKCAST_TOKEN="$(cat config/secrets/management-token)"

Requests below use curl -i to show the HTTP status and headers. Management requests send no body. Response bodies are formatted for readability; example timestamps, counters, limits, IDs, and generations are illustrative, not a capture of your running server. Examples are independent snapshots, not a sequence to run against production. Use IDs, generations, and revisions returned by your own inspection requests.

Successful JSON responses include Content-Type: application/json and Cache-Control: no-store. A successful 204 response has no JSON body. HLS inspection examples require HLS to be enabled and a producer to have created the illustrated stream and rendition.

Endpoint inventory

All GETs are observational and have no lifecycle side effects. All paths are case-sensitive. For continuous mounts, append the complete canonical mount path to /api/mounts or /api/sources: /nested/radio.mp3 becomes /api/mounts/nested/radio.mp3. Separate source resources and a filtered listener collection avoid ambiguity between nested mount paths and reserved suffixes.

GET /api/server

No parameters. 200 returns ServerInfo: name (Quikcast), package version, started_at, uptime_ms, runtime state (accepting, draining, or shutting_down), drain_started_at, drain_age_ms, capabilities, and limits (max_connections, max_listeners, max_handshakes). Startup is runtime construction; binding normally immediately follows it. No build paths, implementation details, configuration secrets, or task identifiers are exposed.

Example request

curl -i -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/server"

Example response: 200 OK

{ "name": "Quikcast", "version": "0.1.0", "started_at": 1791460800000, "uptime_ms": 60000, "state": "accepting", "drain_started_at": null, "drain_age_ms": null, "capabilities": [ "mp3", "aac-adts", "ogg-vorbis", "ogg-opus", "ogg-flac", "flac", "icy", "management", "continuous-relays", "hls-mpeg-ts" ], "limits": { "max_connections": 256, "max_listeners": 128, "max_handshakes": 32 } }

POST /api/server/drain

Requires the same management Bearer token. No query parameters or body. 200 returns the same ServerInfo model as GET /api/server, with state: "draining", a Unix-millisecond drain_started_at, and monotonic drain_age_ms. These fields are null before an explicit drain. Repeated/concurrent POSTs are idempotent and preserve the original start time. Invalid/missing authentication returns 401 unauthorized; body/query validation failures return 400 invalid_request; other methods return 405 with Allow: POST.

Draining is one-way until process restart or shutdown. New continuous source/listener admissions receive HTTP 503 with Server draining\n (typed domain error ServerDraining, native error code server_draining where a native operation rejects admission). Existing source/listener leases continue; administrative disconnect, metadata update, management inspection, health, metrics, and public HLS reads remain available. Continuous HEAD requests inspect headers and do not admit a listener, so remain available.

Continuous admission has an early check before generation/listener reservations and a final atomic accepting observation immediately before publishing ownership. That successful final observation is the admission boundary. If drain is authoritative before it, reservations unwind normally and no record/generation/counter is committed. If the final observation wins, that operation may finish committing and starting after the drain response. TCP connection acceptance, partial headers, authentication, and reservation acquisition alone do not establish admission. Network/handshake limits remain necessary to serve management, health, and HLS, and are not disabled by drain.

HLS option A: after producer authentication, every non-GET producer request checks admission before upload permits, body budgets, or mutations. New stream/rendition creation, segment uploads, publication, master changes, end, and delete are all rejected with HTTP 503. Requests already admitted before drain may finish, including uploading and accepting a segment afterward. Subsequent requests from that same producer are rejected. Producer GET inspection and all public HLS playback remain available. Published playlists stay immutable, and existing retention/expiry rules continue. No active-producer-session exception is introduced.

Drain does not cancel connections, close rings, retire HLS, schedule a shutdown timer, or wait for zero activity. SIGTERM/Ctrl+C/the shutdown token still owns the existing graceful close/join/deadline behavior. During shutdown, server state is shutting_down, which takes precedence over drain state. See the drain lifecycle review for implementation and test details.

Example request

curl -i -X POST -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/server/drain"

Example response: 200 OK

{ "name": "Quikcast", "version": "0.1.0", "started_at": 1791460800000, "uptime_ms": 60000, "state": "draining", "drain_started_at": 1791460860000, "drain_age_ms": 0, "capabilities": [ "mp3", "aac-adts", "ogg-vorbis", "ogg-opus", "ogg-flac", "flac", "icy", "management", "continuous-relays", "hls-mpeg-ts" ], "limits": { "max_connections": 256, "max_listeners": 128, "max_handshakes": 32 } }

Use this only when you intend to drain the server. To verify the resulting state, repeat GET /api/server; repeated drain requests retain the original drain_started_at.

GET /api/stats

No parameters. 200 returns Stats: uptime_ms, configured_mounts, active_mounts, active_sources, active_listeners, active_connections, active_handshakes, source_connections_total, listener_connections_total, bytes_ingested_total, bytes_served_total, slow_consumer_disconnects, authentication_failures, rejected_connections, drain_rejections, hls_requests, hls_segments_served, hls_publication_conflicts, hls_active_uploads, a typed relays summary, and a typed hls summary.

The HLS summary includes stream_count, rendition_count, retained_bytes, allocation_reserved_bytes, allocation_budget, and max_uploads. Disabled HLS reports zero values. Registry counts include active, ended, and deleted objects still retained for grace/ownership cleanup. Retained bytes count stored segment payloads; reservation bytes additionally include backing capacity, playlists, bookkeeping, uploads, and response-held allocations. The two quantities deliberately differ.

Counters are process-local. Active mounts count streaming source leases, rather than configured mounts or HLS streams. Active connections include the administration request itself. Active handshakes count connections reading their bounded request preface. Rejected connections count admission/source-conflict/authentication/drain rejections, not every malformed request. Global ingested bytes count accepted audio ring publication. Served bytes include actual audio/ICY/HLS transport body writes, excluding response headers. HLS segments served counts successful GET responses scheduled for segment delivery, excluding HEAD, 304, and rejected range requests; it does not certify that a remote client consumed the complete segment.

Statistics are an observational sample, not an atomic transaction across every connection. Continuous counters use atomics. HLS summaries traverse the bounded stream/rendition registries and use maintained retained-byte counters; they do not clone segments or scan each segment payload/index. Metrics remain separate at /metrics.

Example request

curl -i -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/stats"

Example response: 200 OK

{ "uptime_ms": 60000, "configured_mounts": 2, "active_mounts": 1, "active_sources": 1, "active_listeners": 1, "active_connections": 3, "active_handshakes": 0, "source_connections_total": 7, "listener_connections_total": 12, "bytes_ingested_total": 944000, "bytes_served_total": 480000, "slow_consumer_disconnects": 0, "authentication_failures": 0, "rejected_connections": 0, "drain_rejections": 0, "relays": { "configured": 1, "connecting": 0, "connected": 0, "backoff": 0, "stopped": 1, "attempts_total": 0, "connections_total": 0, "reconnects_total": 0, "failures_total": 0, "response_body_bytes_total": 0, "audio_bytes_total": 0, "metadata_rejections_total": 0, "allocation_reservation_bytes": 2097152 }, "hls": { "stream_count": 0, "rendition_count": 0, "retained_bytes": 0, "allocation_reserved_bytes": 0, "allocation_budget": 536870912, "max_uploads": 4 }, "hls_requests": 0, "hls_segments_served": 0, "hls_publication_conflicts": 0, "hls_active_uploads": 0 }

GET /api/mounts

No parameters. 200 returns an array of MountInfo, sorted lexicographically by canonical path. Startup restricts configuration to at most 64 mounts, so this list is bounded.

Example request

curl -i -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/mounts"

Example response: 200 OK

[ { "path": "/radio.mp3", "source": { "generation": 7, "connected_at": 1791460801000, "age_ms": 59000, "connection": { "remote_address": "127.0.0.1:53124", "client_ip": "127.0.0.1", "user_agent": "Example encoder" }, "content_type": "audio/mpeg", "format": "mp3", "codec": "mp3", "source_metadata": { "name": "Example Radio", "description": "Live music", "genre": "Various", "url": "https://radio.example.com", "public": false, "declared_bitrate_kbps": 128, "declared_sample_rate_hz": 44100, "declared_channels": 2 }, "track": { "revision": 3, "title": "Example Artist - Example Track" }, "bytes_received": 944000, "state": "streaming" }, "active_listeners": 1, "listener_limit": 64, "bytes_ingested": 944000, "bytes_served": 480000, "state": "accepting" } ]

GET /api/mounts/

No parameters. 200 returns MountInfo; 404 mount_not_found for an unknown mount. A configured mount remains inspectable without a source.

MountInfo fields:

  • path, lifecycle state (accepting, draining, or closed).

  • source: a SourceInfo object, or null when no generation owns the mount.

  • active_listeners, listener_limit (null means unlimited locally).

  • Process-lifetime per-mount bytes_ingested and bytes_served.

Per-mount ingested bytes count encoded input submitted to the source lifecycle, including input subsequently rejected as malformed media; source bytes_received uses the same receive definition. This can differ from globally accepted ring bytes. Served bytes count actual listener body writes including ICY bytes.

Example request

curl -i -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/mounts/radio.mp3"

Example response: 200 OK

{ "path": "/radio.mp3", "source": { "generation": 7, "connected_at": 1791460801000, "age_ms": 59000, "connection": { "remote_address": "127.0.0.1:53124", "client_ip": "127.0.0.1", "user_agent": "Example encoder" }, "content_type": "audio/mpeg", "format": "mp3", "codec": "mp3", "source_metadata": { "name": "Example Radio", "description": "Live music", "genre": "Various", "url": "https://radio.example.com", "public": false, "declared_bitrate_kbps": 128, "declared_sample_rate_hz": 44100, "declared_channels": 2 }, "track": { "revision": 3, "title": "Example Artist - Example Track" }, "bytes_received": 944000, "state": "streaming" }, "active_listeners": 1, "listener_limit": 64, "bytes_ingested": 944000, "bytes_served": 480000, "state": "accepting" }

When the configured mount has no source, source is null and active_listeners is zero. The mount itself remains in the response.

GET /api/sources/

No parameters. 200 returns a SourceInfo object or JSON null when disconnected. Unknown mount returns 404 mount_not_found.

SourceInfo: generation, connected_at, age_ms, connection, content_type, format, codec, typed source_metadata, typed track, bytes_received, and state (connecting, streaming, or disconnecting). Generation is mount-local and monotonically increasing during this process.

connection contains remote_address (socket IP and port), resolved client_ip, and optional user_agent (bounded to 256 characters). format is mp3, aac-adts, ogg-audio, or flac. Ogg codec is null until the existing Ogg parser identifies Vorbis, Opus, or FLAC; chained Ogg updates the known codec. MP3/AAC codec follows the admitted content type.

source_metadata contains optional name, description, genre, url, public, declared_bitrate_kbps, declared_sample_rate_hz, and declared_channels. Audio properties are explicit producer declarations from existing source headers, not measurements. Unknown or malformed declarations are null. track contains revision and title, independently of station metadata and connection state. The initial/cleared title is an empty string. These records belong to the source generation and disappear when its lease ends.

Example request

curl -i -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/sources/radio.mp3"

Example response: 200 OK

{ "generation": 7, "connected_at": 1791460801000, "age_ms": 59000, "connection": { "remote_address": "127.0.0.1:53124", "client_ip": "127.0.0.1", "user_agent": "Example encoder" }, "content_type": "audio/mpeg", "format": "mp3", "codec": "mp3", "source_metadata": { "name": "Example Radio", "description": "Live music", "genre": "Various", "url": "https://radio.example.com", "public": false, "declared_bitrate_kbps": 128, "declared_sample_rate_hz": 44100, "declared_channels": 2 }, "track": { "revision": 3, "title": "Example Artist - Example Track" }, "bytes_received": 944000, "state": "streaming" }

A configured mount with no connected source returns 200 OK with the JSON body null, rather than 404. Native FLAC additionally exposes parsed sample_rate_hz, channels, and bits_per_sample; these fields are omitted when parsed native FLAC properties are unavailable.

DELETE /api/sources/

Optional generation=<unsigned 64-bit integer> query. No body. 204 requests cancellation of the resolved generation through its normal source lifecycle. 404 mount_not_found; 409 source_not_connected if no source; 409 conflict if the requested or resolved generation is stale; 400 invalid_request for invalid parameters.

The operation first obtains an owned target, then validates exact generation identity under mount admission before cancelling it. A delayed action for generation N cannot affect N+1. Supplying generation additionally prevents an operator acting on stale previously inspected state. Without it, the action targets the generation active when this request resolves it.

Cancellation interrupts raw/framed source reads, closes its ring and generation-bound listeners, and lets the owning source lease perform normal accounting and metadata cleanup. It does not release admission early or replace a source. 204 means cancellation accepted, not joined cleanup completed. Duplicate requests may receive 204 while the same generation is disconnecting, then 409 after cleanup. Without an explicit generation, a later separate request can intentionally target a newly resolved source. Administrative disconnects record a typed reason and structured mutation log.

Example request

curl -i -X DELETE -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/sources/radio.mp3?generation=7"

Example response: 204 No Content

HTTP/1.1 204 No Content Cache-Control: no-store

There is no response body.

GET /api/listeners?mount=&limit=&after=

mount is required and must exactly name a configured mount. limit defaults to 100 and must be 1–200. after is an optional opaque listener ID. Use curl's query encoding:

200 returns ListenerPage: items (at most limit ListenerInfo records) and next_after (null at the end, otherwise pass it unchanged). 400 invalid_pagination for missing/invalid/duplicate/unknown parameters; 404 mount_not_found for an unknown mount.

Ordering is ascending lexical listener ID, with the cursor strictly exclusive. IDs are random, so this is not chronological order. Pagination is a live view: listeners that leave disappear; additions sorting before an already passed cursor can be missed until a fresh listing. A cursor need not remain active. Each page locks only that mount's registry, visits at most limit+1 entries, and constructs only the requested page. There is no global listener list clone or persistent history.

Example request

curl -i -G -H "Authorization: Bearer $QUIKCAST_TOKEN" \ --data-urlencode "mount=/radio.mp3" \ --data-urlencode "limit=1" \ "$QUIKCAST_ADMIN_URL/api/listeners"

Example response: 200 OK

{ "items": [ { "id": "0123456789abcdef0123456789abcdef", "mount": "/radio.mp3", "generation": 7, "connected_at": 1791460830000, "duration_ms": 30000, "connection": { "remote_address": "127.0.0.1:54210", "client_ip": "127.0.0.1", "user_agent": "Example player" }, "bytes_sent": 480000, "icy_metadata": false, "state": "connected" } ], "next_after": "0123456789abcdef0123456789abcdef" }

This example assumes another listener follows this page. Fetch the next page by adding --data-urlencode "after=0123456789abcdef0123456789abcdef" to the same request. The final page has next_after: null; an empty collection is {"items":[],"next_after":null}.

GET /api/listeners/

No parameters. 200 returns ListenerInfo; 404 listener_not_found for an invalid/unknown/no-longer-active ID.

ListenerInfo: opaque id (32 lowercase hexadecimal characters from 128 random bits), canonical serving mount, requested_mount, serving_mount, owning source generation, connected_at, duration_ms, connection, bytes_sent (actual body transport writes, including ICY bytes), icy_metadata, and state (connected or disconnecting). Incoming forwarding headers are not exposed. Random IDs do not disclose memory addresses or reusable slot indexes. Entropy failure rejects admission; an active-ID collision fails closed.

Example request

curl -i -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/listeners/0123456789abcdef0123456789abcdef"

Example response: 200 OK

{ "id": "0123456789abcdef0123456789abcdef", "mount": "/radio.mp3", "generation": 7, "connected_at": 1791460830000, "duration_ms": 30000, "connection": { "remote_address": "127.0.0.1:54210", "client_ip": "127.0.0.1", "user_agent": "Example player" }, "bytes_sent": 480000, "icy_metadata": false, "state": "connected" }

DELETE /api/listeners/

No parameters or body. 204 requests cancellation; 404 listener_not_found for invalid/unknown/completed listeners; 400 invalid_request for query parameters.

The domain marks the administrative reason and cancels that record's token. The existing supervised connection monitor/body terminates the transport; lease Drop removes the ephemeral record and releases the same local/global permits as any normal close. The handler never owns or edits sockets. Concurrent duplicate operations are safe and may return 204 until cleanup, then 404. Cancellation is observed by the existing monitor at its 50 ms tick even if no source audio arrives. 204 does not promise completed task joining. A stale ID cannot select a new connection through a reused index. Mutation logs include the opaque listener ID, mount, result, and reason.

Example request

curl -i -X DELETE -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/listeners/0123456789abcdef0123456789abcdef"

Example response: 204 No Content

HTTP/1.1 204 No Content Cache-Control: no-store

There is no response body.

GET /api/hls

No parameters. 200 returns bounded StreamInfo[], sorted by stream name. Disabled HLS returns 404 hls_not_found. Startup limits registry capacity to at most 64 streams.

Example request

curl -i -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/hls"

Example response: 200 OK

[ { "name": "radio", "generation": 101, "state": "active", "playlist_revision": 2, "rendition_count": 1, "retained_bytes": 192000 } ]

Enabled HLS with no registered streams returns []. Disabled HLS returns 404 hls_not_found.

GET /api/hls/

No parameters. 200 returns StreamInfo; 404 hls_not_found if unknown or HLS disabled. StreamInfo: name, generation, state (active, ended, deleted), master playlist_revision, rendition_count, and retained segment retained_bytes.

Example request

curl -i -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/hls/radio"

Example response: 200 OK

{ "name": "radio", "generation": 101, "state": "active", "playlist_revision": 2, "rendition_count": 1, "retained_bytes": 192000 }

GET /api/hls//renditions

No parameters. 200 returns bounded RenditionInfo[], sorted by rendition name; 404 hls_not_found for unknown stream or disabled HLS. Configured bounds allow at most 16 renditions per stream and 128 across the server.

Example request

curl -i -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/hls/radio/renditions"

Example response: 200 OK

[ { "name": "audio", "generation": 102, "state": "active", "bandwidth": 128000, "codecs": "mp4a.40.2", "target_duration": 4, "playlist_revision": 5, "media_sequence": 10, "advertised_segment_count": 3, "retained_segment_count": 3, "retained_bytes": 192000, "allocation_reserved_bytes": 262144, "allocation_budget": 134217728, "last_segment_ingest_at": 1791460858000, "last_playlist_publication_at": 1791460859000 } ]

GET /api/hls//renditions/

No parameters. 200 returns RenditionInfo; 404 hls_not_found for unknown stream/rendition or disabled HLS.

RenditionInfo: name, generation, state (active, ended, deleted), producer-declared bandwidth and codecs, target_duration (seconds), playlist_revision, media_sequence (first currently advertised sequence or null), advertised_segment_count, retained_segment_count, retained_bytes, allocation_reserved_bytes, allocation_budget, last_segment_ingest_at, and last_playlist_publication_at.

Retained segment bytes include staging and grace-period objects. Deleted tombstones remain inspectable until existing maintenance and final references permit reclamation; after that lookup returns 404. Read snapshots lock registry/state synchronously and never await or clone media payloads. Different resources need not represent one atomic cross-stream instant. The producer API remains responsible for HLS end/delete; /api does not duplicate it. Continuous streaming and HLS remain independent.

Example request

curl -i -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/hls/radio/renditions/audio"

Example response: 200 OK

{ "name": "audio", "generation": 102, "state": "active", "bandwidth": 128000, "codecs": "mp4a.40.2", "target_duration": 4, "playlist_revision": 5, "media_sequence": 10, "advertised_segment_count": 3, "retained_segment_count": 3, "retained_bytes": 192000, "allocation_reserved_bytes": 262144, "allocation_budget": 134217728, "last_segment_ingest_at": 1791460858000, "last_playlist_publication_at": 1791460859000 }

Errors, times, and health

API errors have a stable native envelope:

{"error":{"code":"mount_not_found","message":"Mount not found"}}

Shared endpoint errors: 401 unauthorized, 400 invalid_request, 405 method_not_allowed, 404 endpoint_not_found, and 500 internal_error for domain/serialization faults. HLS malformed identifiers receive 400 invalid_request; state conflicts receive 409 conflict. Error responses never contain secret values, credentials, filesystem paths, debug types, or stack traces. Internal faults retain diagnostic log context. Admission rejected because of drain uses 503 server_draining when surfaced through a native API operation. Endpoint-specific errors are listed above.

Timestamp fields ending in _at, plus connected_at/started_at, are Unix epoch milliseconds. Duration/age/uptime use monotonic elapsed milliseconds. Counters and identifiers are ephemeral across restarts; do not use source generation alone as a cross-restart identity.

Public responses identify as Server: Quikcast. Continuous audio remains HTTP/1.0 close-delimited with no conflicting Content-Length/Transfer-Encoding, existing content/ICY headers, and Cache-Control: no-cache, no-store.

/health/live confirms the running request service and stays 200 while draining. /health/ready returns 200 only while accepting, and becomes 503 with Server draining\n after drain; during shutdown it follows the existing 503 unavailable response. Readiness probes do not increment drain admission rejection counters. Configuration and subsystem initialization finish before binding. Neither requires connected sources, listeners, or HLS streams. Both remain constant-cost. /metrics continues to expose a fixed bounded-cardinality Prometheus inventory; no listener IDs, addresses, user agents, or dynamic mount labels have been added.

Example error responses

Missing authentication:

curl -i "$QUIKCAST_ADMIN_URL/api/server"

401 Unauthorized, with WWW-Authenticate: Bearer realm="Quikcast management":

{"error":{"code":"unauthorized","message":"Authentication required"}}

Unknown mount:

curl -i -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/mounts/missing.mp3"

404 Not Found:

{"error":{"code":"mount_not_found","message":"Mount not found"}}

Invalid listener pagination:

curl -i -G -H "Authorization: Bearer $QUIKCAST_TOKEN" \ --data-urlencode 'mount=/radio.mp3' --data-urlencode 'limit=201' \ "$QUIKCAST_ADMIN_URL/api/listeners"

400 Bad Request:

{"error":{"code":"invalid_pagination","message":"Invalid listener pagination"}}

A source DELETE with a stale generation on a mount that currently has a different source returns 409 Conflict:

{"error":{"code":"conflict","message":"Source generation changed"}}

If the mount exists but has no source, source DELETE returns 409 Conflict:

{"error":{"code":"source_not_connected","message":"Source not connected"}}

Relay POST/DELETE with a stale revision returns 409 Conflict:

{"error":{"code":"conflict","message":"Resource changed"}}

A relay POST during drain returns 503 Service Unavailable:

{"error":{"code":"server_draining","message":"Server draining"}}

An unsupported method returns 405 Method Not Allowed, with the allowed methods in the Allow header:

{"error":{"code":"method_not_allowed","message":"Method not allowed"}}

Trusted proxy contract

Only X-Forwarded-For is supported. The immediate transport peer must match a configured exact IP or CIDR in trusted_proxies; otherwise the complete header is ignored. Address families match literally, including IPv4-mapped IPv6: configure the actual socket family explicitly.

For a trusted peer, accept exactly one header, at most 1024 bytes, containing at most 16 comma-separated plain IP addresses. Starting from the socket peer, walk right to left while the current hop is trusted; the first untrusted hop becomes the client address. Never skip past that untrusted hop to an attacker-supplied leftmost address. If all hops are trusted, the leftmost address is selected. Empty, malformed, oversized, overlong-chain, duplicate, port-bearing, or non-IP entries invalidate the whole header and fall back to the socket peer. No RFC Forwarded, X-Real-IP, or PROXY protocol is interpreted.

The proxy must append the actual upstream address or replace untrusted incoming forwarding headers, and trusted CIDRs must correspond only to infrastructure you control. This identity is for operational inspection; it does not change authentication or admission policy.

Drain observability uses the existing ready gauge (0 during drain), /api/server state/start/age, a fixed drain_rejections_total Prometheus counter, and /api/stats.drain_rejections. Rejected source/listener/HLS producer requests also increment existing admission rejection accounting. Health and inspection polls do not. A structured log records the first transition only; repeated POSTs do not restart its clock or restore acceptance.

Continuous relay operations

Use these endpoints for configured continuous relays. The complete canonical mount path follows /api/relays; the upstream URL and credentials cannot be changed through this API. Relay commands have no body, accept an optional revision guard, and return 202 with a snapshot. Control revision and source generation are different identifiers.

GET /api/relays

Returns an array of all configured relays; no relays returns []. No query parameters.

Example request

curl -i -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/relays"

Example response: 200 OK

[ { "mount": "/remote.mp3", "state": "connected", "desired_running": true, "revision": 4, "attempt": 2, "generation": 9, "connected_at": 1791460830000, "connection_age_ms": 30000, "next_retry_at": null, "retry_in_ms": null, "retry_delay_ms": null, "stopped_reason": null, "last_error": null, "last_upstream_status": 200, "attempts_total": 2, "connections_total": 2, "reconnects_total": 1, "response_body_bytes_total": 481024, "audio_bytes_total": 480000, "metadata_rejections_total": 0, "allocation_reservation_bytes": 2097152 } ]

GET /api/relays/

Inspect a configured relay. No query parameters. Unknown relay returns 404 relay_not_found.

Example request

curl -i -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/relays/remote.mp3"

Example response: 200 OK

{ "mount": "/remote.mp3", "state": "connected", "desired_running": true, "revision": 4, "attempt": 2, "generation": 9, "connected_at": 1791460830000, "connection_age_ms": 30000, "next_retry_at": null, "retry_in_ms": null, "retry_delay_ms": null, "stopped_reason": null, "last_error": null, "last_upstream_status": 200, "attempts_total": 2, "connections_total": 2, "reconnects_total": 1, "response_body_bytes_total": 481024, "audio_bytes_total": 480000, "metadata_rejections_total": 0, "allocation_reservation_bytes": 2097152 }

POST /api/relays/

Start a stopped relay or reconnect a running relay. Read its current revision first; this example uses revision 4.

Example request

curl -i -X POST -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/relays/remote.mp3?revision=4"

Example response: 202 Accepted

{ "mount": "/remote.mp3", "state": "connected", "desired_running": true, "revision": 5, "attempt": 2, "generation": 9, "connected_at": 1791460830000, "connection_age_ms": 30000, "next_retry_at": null, "retry_in_ms": null, "retry_delay_ms": null, "stopped_reason": null, "last_error": null, "last_upstream_status": 200, "attempts_total": 2, "connections_total": 2, "reconnects_total": 1, "response_body_bytes_total": 481024, "audio_bytes_total": 480000, "metadata_rejections_total": 0, "allocation_reservation_bytes": 2097152 }

202 means the command was accepted. The response can still show the prior connected generation while the worker cancels it. Poll GET /api/relays/remote.mp3 to observe subsequent connecting/connected/backoff state. A stale revision returns 409 conflict; a start/reconnect during drain returns 503 server_draining.

DELETE /api/relays/

Stop automatic pulling and retry. This independent example assumes the latest inspected revision is 4.

Example request

curl -i -X DELETE -H "Authorization: Bearer $QUIKCAST_TOKEN" \ "$QUIKCAST_ADMIN_URL/api/relays/remote.mp3?revision=4"

Example response: 202 Accepted

{ "mount": "/remote.mp3", "state": "connected", "desired_running": false, "revision": 5, "attempt": 2, "generation": 9, "connected_at": 1791460830000, "connection_age_ms": 30000, "next_retry_at": null, "retry_in_ms": null, "retry_delay_ms": null, "stopped_reason": "administrative_stop", "last_error": null, "last_upstream_status": 200, "attempts_total": 2, "connections_total": 2, "reconnects_total": 1, "response_body_bytes_total": 481024, "audio_bytes_total": 480000, "metadata_rejections_total": 0, "allocation_reservation_bytes": 2097152 }

The worker stops asynchronously. Poll until state is stopped; a 202 response can still show state: "connected" during cleanup. Stop remains available during drain. A stale revision returns 409 conflict.

See the continuous relay contract for every snapshot field, retry behavior, counters, and lifecycle semantics. /api/stats.relays provides aggregate visibility.

Fallback visibility

Configured continuous mount snapshots include fallback (canonical target or null). Listener records add requested_mount and serving_mount. The existing mount field continues to mean the serving mount; listener pagination filters that mount's registry. Active counts and body bytes belong to the serving mount only. /api/stats and Prometheus expose the fixed unlabeled fallback_listener_admissions_total; /api/server includes capability fallback-mounts. See Fallback mounts for routing, metadata and restoration semantics.

Listener access inspection

/api/server advertises listener-access-control and includes sanitized global access rule counts/country lists, geoip_loaded, and mount_access_enabled. Continuous mount inspection includes the same sanitized access model for that mount's own restrictions. IP and UA rule contents and MMDB paths are omitted. Global policy remains a prerequisite for every mount and public HLS playback. See Listener access control for configuration, limits, deny counters and fallback behavior. These are read-only inspection fields; management authentication is unchanged.

08 October 2026