Skip to content

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.

GET /api/metadata?mount=...&mode=updinfo&song=... is a separate source operation on the streaming listener (listen). It uses the requested mount's source credentials via HTTP Basic authentication and returns the ICY XML response. It does not use the management bearer token.

Configuration and authentication ​

toml
[trusted_proxies] # optional
ips = ["127.0.0.1", "10.20.0.0/16"]
providers = [] # Or ["cloudflare"] for direct Cloudflare peers.

[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:

sh
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:

sh
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:

sh
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, config_generation, config_committed_at, 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

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

Example response: 200 OK

json
{
  "name": "Quikcast",
  "version": "0.1.0",
  "started_at": 1791460800000,
  "uptime_ms": 60000,
  "config_generation": 1,
  "config_committed_at": 1791460800000,
  "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",
    "hls-mpeg-ts-aac",
    "hls-packed-aac",
    "hls-packed-mp3",
    "hls-fmp4-aac",
    "hls-fmp4-flac"
  ],
  "limits": {
    "max_connections": 256,
    "max_listeners": 128,
    "max_handshakes": 32
  }
}

Configuration generation starts at 1 and increments only after successful atomic reload. config_committed_at is Unix epoch milliseconds. These fields and policy inspection use the same admission snapshot as authentication for that API request. See SIGHUP reload for field classification, failure behavior, secret rotation and relay handoff. There is no management reload endpoint. The atomic-config-reload capability describes snapshot support; the executable provides its Unix signal trigger.

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.

Example request

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

Example response: 200 OK

json
{
  "name": "Quikcast",
  "version": "0.1.0",
  "started_at": 1791460800000,
  "uptime_ms": 60000,
  "config_generation": 1,
  "config_committed_at": 1791460800000,
  "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",
    "hls-mpeg-ts-aac",
    "hls-packed-aac",
    "hls-packed-mp3",
    "hls-fmp4-aac",
    "hls-fmp4-flac"
  ],
  "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 full (200) and partial (206) 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

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

Example response: 200 OK

json
{
  "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

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

Example response: 200 OK

json
[
  {
    "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

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

Example response: 200 OK

json
{
  "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). Configured-mount generations are mount-local and monotonically increasing during this process. Dynamic generations use a daemon-wide monotonic sequence, so recreating a dynamic path cannot reuse an earlier generation guard.

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

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

Example response: 200 OK

json
{
  "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 triggers compatible continuous listeners to resolve their configured fallback (other listeners disconnect), 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

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

Example response: 204 No Content

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

There is no response body.

GET /api/listeners?mount={canonical path}&limit={n}&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

sh
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

json
{
  "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

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

Example response: 200 OK

json
{
  "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

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

Example response: 204 No Content

http
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

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

Example response: 200 OK

json
[
  {
    "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

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

Example response: 200 OK

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

GET /api/hls/{stream}/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

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

Example response: 200 OK

json
[
  {
    "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,
  "format": "mpeg-ts",
  "container": "mpeg-ts",
  "codec": "aac",
  "content_type": "video/mp2t",
  "initialization_object": null,
  "initialization_object_count": 0,
  "media_object_count": 3,
  "audio_properties": null,
    "format": "mpeg-ts",
    "container": "mpeg-ts",
    "codec": "aac",
    "content_type": "video/mp2t",
    "initialization_object": null,
    "initialization_object_count": 0,
    "media_object_count": 3,
    "audio_properties": null,
    "retained_bytes": 192000,
    "allocation_reserved_bytes": 262144,
    "allocation_budget": 134217728,
    "last_segment_ingest_at": 1791460858000,
    "last_playlist_publication_at": 1791460859000
  }
]

GET /api/hls/{stream}/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.

Generalized audio inspection also includes format (mpeg-ts, packed-aac, packed-mp3, fmp4), container (mpeg-ts, packed, fmp4), codec, canonical content_type, nullable current initialization_object name, initialization_object_count, media_object_count and nullable audio_properties (sample_rate_hz, channels, nullable bits_per_sample). Retained bytes include initialization objects. Current initialization is null until a playlist publishes it; held/grace objects remain charged independently. See the audio object contract for typed ingest, lifecycle and supported codecs. No object bytes or producer credentials are exposed.

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

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

Example response: 200 OK

json
{
  "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,
  "format": "mpeg-ts",
  "container": "mpeg-ts",
  "codec": "aac",
  "content_type": "video/mp2t",
  "initialization_object": null,
  "initialization_object_count": 0,
  "media_object_count": 3,
  "audio_properties": null,
  "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:

json
{
  "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:

sh
curl -i "$QUIKCAST_ADMIN_URL/api/server"

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

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

Unknown mount:

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

404 Not Found:

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

Invalid listener pagination:

sh
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:

json
{
  "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:

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

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

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

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

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

A relay POST during drain returns 503 Service Unavailable:

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

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

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

Trusted proxy contract ​

A direct peer matched to the enabled cloudflare provider may supply one plain IP in CF-Connecting-IP. Otherwise the existing bounded X-Forwarded-For chain is used for peers matched by trusted_proxies.ips or provider networks. Untrusted peers cannot override their client IP, and manually trusted proxies do not gain Cloudflare header authority. IPv4-mapped IPv6 addresses normalize to IPv4 consistently. All inspection uses this shared resolved identity.

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.

Relay snapshots include ordered failover inspection: upstream_count (1–8), current_upstream_index (zero-based or null), current_upstream (origin only, no path/query), upstream_usable (normal codec/media readiness), attempt_cycle, consecutive_failures, and last_failure (fixed last remote category, retained across connection). Connected state means source ownership; Ogg can still await initialization with usability false. Backoff/stopped state has null selection and false usability. Cycle increments at index-0 attempts. Consecutive remote failures reset on explicit control revision changes or when a stable attempt ends. Worker replacement resets these observations. Upstream credentials, secret paths, URL paths/queries and raw errors are never exposed.

Failover is sticky: remote failure selects the next candidate after cleanup, exhaustion backs off then retries primary, and recovery of the primary does not displace a healthy backup. POST always resets selection to primary; DELETE and source DELETE suppress failover/retry. Drain blocks successors, while admitted connections continue. The fixed unlabeled /metrics counters relay_failovers_total and relay_upstream_failures_total distinguish forward candidate transitions from genuine remote failures; administrative actions and local mount conflicts do not count. URL/list editing remains file-based through atomic reload. An unchanged list preserves a healthy backup; any effective list/credential edit restarts that worker from primary.

GET /api/relays ​

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

Example request

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

Example response: 200 OK

json
[
  {
    "mount": "/remote.mp3",
    "upstream_count": 1,
    "current_upstream_index": 0,
    "current_upstream": "https://origin.example",
    "upstream_usable": true,
    "attempt_cycle": 2,
    "consecutive_failures": 0,
    "last_failure": null,
    "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

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

Example response: 200 OK

json
{
  "mount": "/remote.mp3",
  "upstream_count": 1,
  "current_upstream_index": 0,
  "current_upstream": "https://origin.example",
  "upstream_usable": true,
  "attempt_cycle": 2,
  "consecutive_failures": 0,
  "last_failure": null,
  "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

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

Example response: 202 Accepted

json
{
  "mount": "/remote.mp3",
  "upstream_count": 1,
  "current_upstream_index": 0,
  "current_upstream": "https://origin.example",
  "upstream_usable": true,
  "attempt_cycle": 2,
  "consecutive_failures": 0,
  "last_failure": null,
  "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

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

Example response: 202 Accepted

json
{
  "mount": "/remote.mp3",
  "upstream_count": 1,
  "current_upstream_index": 0,
  "current_upstream": "https://origin.example",
  "upstream_usable": true,
  "attempt_cycle": 2,
  "consecutive_failures": 0,
  "last_failure": null,
  "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 history ​

GET /api/history reports volatile listener history for the whole server; add mount=/radio.mp3 for one mount (percent-encode nested paths) and minutes=N (1–1440, default 60) to limit the per-minute window. History is held in bounded memory, covers the last 24 hours, and resets on restart. A dynamic mount's history ends when the mount is cleaned up; the server-wide view is unaffected.

json
{
  "mount": null,
  "window_minutes": 60,
  "retention_minutes": 1440,
  "peak_listeners_24h": 41,
  "listener_starts_24h": 318,
  "unique_listeners_estimate_24h": 207,
  "minutes": [{"start_ms": 1790000000000, "peak_listeners": 12, "listener_starts": 3}],
  "hours": [{"start_ms": 1789999200000, "unique_listeners_estimate": 88}]
}

peak_listeners is the highest concurrent listener count seen in that minute; quiet minutes carry the current audience forward. listener_starts counts admissions. Unique counts are HyperLogLog estimates (about 3% error) per hour and merged over 24 hours; client addresses are hashed into the sketch with a per-process random key and are never stored. The listener-history capability appears in /api/server. Invalid parameters return 400 invalid_request; an unknown mount returns 404 mount_not_found.

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.

Source IP access inspection ​

Mount snapshots include source_access with deduplicated ip_allow_rules and ip_deny_rules counts; omitted policy has zero counts. IP rules, credentials and secret-file paths are not exposed. Server capabilities include source-ip-access-control. Private /metrics exposes the fixed unlabeled source_access_denied_ip_total counter separately from authentication and source admission accounting. There are no source ACL mutation endpoints. See Source ingest IP access control for policy, proxy resolution and protocol ordering.

Signed listener URL authentication ​

See signed expiring listener URLs for mount-local and HLS stream configuration, exact signing rules, external PHP signing, atomic key rotation, sanitized management fields, and protected cache/Range semantics.

See Operational stats and observability for additive fields, exact byte semantics, reason enums and the complete metric inventory.

Continuous mount inspection includes a boolean dynamic: configured mounts report false; ephemeral source-created mounts report true. Dynamic mounts participate in the existing mount, source and listener inspection/disconnect endpoints and disappear after their source and listeners close. Credentials are never included.

Documentation for Quikcast.