Skip to content

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) and X-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 .m3u8 files. 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 ​

RequestWhat it does
PUT SCreate a stream. Empty body. Returns 201 with its generation.
GET SRead the stream's generation, master revision and state.
PUT RCreate a rendition from a JSON description. Needs X-HLS-Generation.
GET RRead 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/publicationAtomically publish the media playlist.
PUT S/masterAtomically publish the master playlist. An empty rendition list withdraws it.
POST R/endPublish the current window with ENDLIST.
POST S/endEnd the stream once all its renditions are ended.
DELETE RHide a rendition's playlist and retire its data. Withdraw it from the master first.
DELETE SHide the stream and retire every rendition.

Create a rendition ​

json
{
  "format": "fmp4",
  "target_duration": 3,
  "window_segments": 6,
  "bandwidth": 128000,
  "codecs": "fLaC"
}
FieldMeaning
formatmpeg-ts (default), packed-aac, packed-mp3 or fmp4.
target_durationSegment length in seconds, 1–60. Fixed for this rendition.
window_segmentsSegments kept in the live playlist, 3–256. Subject to your max_segments budget.
bandwidthPeak bits per second you declare. A positive number.
codecsThe 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 ​

FormatCodecsSegmentContent type
mpeg-tsmp4a.40.2 (AAC).tsvideo/mp2t
packed-aacmp4a.40.2.aacaudio/aac
packed-mp3mp4a.40.34 (alias mp3).mp3audio/mpeg
fmp4mp4a.40.2.m4s + init .mp4audio/mp4
fmp4fLaC (alias flac).m4s + init .mp4audio/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/mp4
  • X-HLS-Duration-Ms is the real duration in milliseconds. It must match target_duration after rounding to the nearest second.
  • X-HLS-Discontinuity is optional (0 or 1).
  • 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 a 409; nothing is overwritten.

What is validated ​

FormatChecks
MPEG-TSWhole 188-byte packets, valid sync bytes and adaptation fields, no scrambling, and an initial valid PAT and PMT.
Packed AAC/MP3Starts with an ID3v2 tag holding the com.apple.streaming.transportStreamTimestamp timestamp, then complete, contiguous frames. You supply the timestamp.
fMP4One 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/mp4

Initialization 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
}
  • initialization is 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 ​

CodeMeaning
201Created or accepted.
400Bad syntax or an invalid description or structure.
401Wrong credentials.
404Unknown stream, rendition or object.
409Stale generation or revision, duplicate or retired sequence, missing or mismatched object, or an illegal window change.
413Upload larger than max_segment_bytes (initializations: the smaller of that and 256 KiB).
415Unsupported content type or codec.
503Out 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 W needs 2W + 1 + max_uploads object slots (plus one for fMP4 initialization), within max_segments.
  • Everything is in memory and is lost on restart.

Public side ​

Players fetch:

URLContent
/hls/{stream}/master.m3u8The master playlist
/hls/{stream}/{rendition}/index.m3u8A media playlist
/hls/{stream}/{rendition}/<generation>-<n>.ts|aac|mp3|m4sA segment
/hls/{stream}/{rendition}/<generation>-init-<id>.mp4An 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.

Documentation for Quikcast.