Appearance
HLS ingest API
The private API your producer uses to publish HLS audio. For a gentle introduction read HLS for browsers and apps first.
All routes are on the admin address (admin_listen) under /internal/v1/hls/, use HTTP Basic authentication with the [hls] username and password, and are separate from the management API and its bearer token.
Conventions
- Names. A stream or rendition name is 1–64 letters, digits,
_or-. Names are never used as file paths. - Generations. Creating a stream or rendition returns a generation, as a JSON string. Send it back, unchanged, in
X-HLS-Generation(stream) andX-HLS-Rendition-Generation(rendition) on later calls. A stale generation is rejected, which stops an old producer from damaging its replacement. - Revisions. Every publication has a revision starting at
0. You send the revision you expect; a mismatch is rejected. - Numbers. Sequence numbers and IDs are plain unsigned decimals: no sign, no leading zeros (except
0). - JSON. Send
Content-Type: application/json. Unknown fields are rejected. Bodies are at most 256 KiB. - No playlists. You never upload
.m3u8files. Quikcast writes playlists from the structured data you publish.
In the table below, S is /internal/v1/hls/streams/{stream} and R is S/renditions/{rendition}.
Endpoints
| Request | What it does |
|---|---|
PUT S | Create a stream. Empty body. Returns 201 with its generation. |
GET S | Read the stream's generation, master revision and state. |
PUT R | Create a rendition from a JSON description. Needs X-HLS-Generation. |
GET R | Read the rendition's generation, revision, state and segment count. |
PUT R/segments/{n} | Upload media segment n. |
PUT R/initializations/{id} | Upload an fMP4 initialization object (fMP4 only). |
PUT R/publication | Atomically publish the media playlist. |
PUT S/master | Atomically publish the master playlist. An empty rendition list withdraws it. |
POST R/end | Publish the current window with ENDLIST. |
POST S/end | End the stream once all its renditions are ended. |
DELETE R | Hide a rendition's playlist and retire its data. Withdraw it from the master first. |
DELETE S | Hide the stream and retire every rendition. |
Create a rendition
json
{
"format": "fmp4",
"target_duration": 3,
"window_segments": 6,
"bandwidth": 128000,
"codecs": "fLaC"
}| Field | Meaning |
|---|---|
format | mpeg-ts (default), packed-aac, packed-mp3 or fmp4. |
target_duration | Segment length in seconds, 1–60. Fixed for this rendition. |
window_segments | Segments kept in the live playlist, 3–256. Subject to your max_segments budget. |
bandwidth | Peak bits per second you declare. A positive number. |
codecs | The RFC 6381 codecs string, up to 128 characters. |
You are responsible for truthful declarations: Quikcast checks the structure of what you upload but never decodes audio or invents bitrates.
Supported combinations
| Format | Codecs | Segment | Content type |
|---|---|---|---|
mpeg-ts | mp4a.40.2 (AAC) | .ts | video/mp2t |
packed-aac | mp4a.40.2 | .aac | audio/aac |
packed-mp3 | mp4a.40.34 (alias mp3) | .mp3 | audio/mpeg |
fmp4 | mp4a.40.2 | .m4s + init .mp4 | audio/mp4 |
fmp4 | fLaC (alias flac) | .m4s + init .mp4 | audio/mp4 |
AAC means AAC-LC only. HE-AAC is rejected.
Upload a segment
text
PUT R/segments/0
Authorization: Basic ...
X-HLS-Generation: <stream generation>
X-HLS-Rendition-Generation: <rendition generation>
X-HLS-Duration-Ms: 2000
X-HLS-Discontinuity: 0
Content-Type: audio/mp4X-HLS-Duration-Msis the real duration in milliseconds. It must matchtarget_durationafter rounding to the nearest second.X-HLS-Discontinuityis optional (0or1).- For fMP4 also send
X-HLS-Initialization: <init id>, which must be an already uploaded, compatible initialization object. - Success is
201. A duplicate sequence is a409; nothing is overwritten.
What is validated
| Format | Checks |
|---|---|
| MPEG-TS | Whole 188-byte packets, valid sync bytes and adaptation fields, no scrambling, and an initial valid PAT and PMT. |
| Packed AAC/MP3 | Starts with an ID3v2 tag holding the com.apple.streaming.transportStreamTimestamp timestamp, then complete, contiguous frames. You supply the timestamp. |
| fMP4 | One audio track, supported codec, self-contained, then one moof/mdat pair per segment matching the initialization. Encryption and multiple tracks are rejected. |
fMP4 initialization
Upload this before the segments that use it:
text
PUT R/initializations/1
Content-Type: audio/mp4Initialization IDs are immutable: uploading an existing ID is a 409. To change to a compatible initialization, upload a new ID, mark its first fragment as a discontinuity, and publish a window that uses only the new one. A change in decoder configuration needs a new rendition.
Publish a playlist
Upload first, then publish. Publication lists consecutive sequences you have uploaded:
json
{
"expected_revision": 0,
"initialization": 1,
"segments": [
{ "sequence": 0, "duration_ms": 2000 },
{ "sequence": 1, "duration_ms": 2000 }
],
"end": false
}initializationis required for fMP4 and forbidden otherwise.- A segment may add
"discontinuity": true, matching its upload. - Every referenced segment must already exist. If any check fails, nothing changes.
- A sliding window must stay at least three target durations long.
The master playlist lists renditions that are ready:
json
{ "expected_revision": 0, "renditions": ["aac"] }Ending a rendition: POST R/end with {"expected_revision": 1}.
Playlists are version 3 for TS and packed audio, and version 7 for fMP4.
Status codes
| Code | Meaning |
|---|---|
201 | Created or accepted. |
400 | Bad syntax or an invalid description or structure. |
401 | Wrong credentials. |
404 | Unknown stream, rendition or object. |
409 | Stale generation or revision, duplicate or retired sequence, missing or mismatched object, or an illegal window change. |
413 | Upload larger than max_segment_bytes (initializations: the smaller of that and 256 KiB). |
415 | Unsupported content type or codec. |
503 | Out of upload slots, memory or registry capacity. Draining also refuses new work. |
Timing and retention
- Upload bodies must keep flowing (10 seconds of silence aborts them), and finish within 30 seconds.
- Uploaded but unpublished objects expire after 30 seconds.
- Advertised segments never expire. Once removed from the playlist, a segment stays available for its own duration plus the longest playlist that listed it, as HLS requires.
- A window
Wneeds2W + 1 + max_uploadsobject slots (plus one for fMP4 initialization), withinmax_segments. - Everything is in memory and is lost on restart.
Public side
Players fetch:
| URL | Content |
|---|---|
/hls/{stream}/master.m3u8 | The master playlist |
/hls/{stream}/{rendition}/index.m3u8 | A media playlist |
/hls/{stream}/{rendition}/<generation>-<n>.ts|aac|mp3|m4s | A segment |
/hls/{stream}/{rendition}/<generation>-init-<id>.mp4 | An initialization object |
GET and HEAD are supported, with ETag revalidation (304) and single byte ranges (206; invalid ones return 416). Playlists use Cache-Control: no-cache; segments are public, max-age=31536000, immutable. With cors_origin set, OPTIONS preflight is answered, allowing the Range, If-None-Match and If-Range headers.
Inspection
Operators can see the state of HLS streams through the management API (/api/hls), including formats, object counts and memory use.