Overlay Streaming
Overlay streaming composites a live web page — a scoreboard, a lower-third, a sponsor bar, any HTML — on top of a live video stream, and publishes the combined result as its own playable stream. The video comes in over RTMP or WebRTC; Native Frame renders your web page to images with the html2img plugin and burns them onto the video; viewers play back the composited output.
This guide drives the whole flow from the API. To set it up from the dashboard with no code, see Overlay Streaming (Dashboard).
Overlay streaming builds on the v2 stream model. If "program", "stream", and "service-account token" are new, skim the Program API reference first — this guide assumes you can already create a program and a stream.
How it works
An overlay is two streams under the same program:
- A source stream (
kind: "source") — the raw video you ingest into. - An overlay stream (
kind: "filtergraph") — the composited output. It points back at the source viaderivedFromId, and it's the stream your viewers play.
Every stream response carries two fields that describe this relationship:
| Field | Type | Description |
|---|---|---|
kind | string | "source" for a regular stream, or "filtergraph" for an overlay. Defaults to "source" when omitted on create. |
derivedFromId | string | null | For an overlay (filtergraph) stream, the id of the source stream it composites on top of. null for source streams. |
The platform identifies an overlay by these two fields — not by name. An Overlay- name prefix is optional and just a human-readable label.
Step 1 — Create the streams
Create a program, then a source stream under it, then an overlay stream under the same program.
Create the source stream — a normal POST /program/api/v2/streams with kind omitted (defaults to source):
POST /program/api/v2/streams
Authorization: Bearer <serviceAccountJWT>
Content-Type: application/json
{
"streamName": "My Stream",
"authKey": "my-auth-key",
"authType": "public",
"transcode": true,
"programID": "{programId}",
"programSlug": "my-stream",
"apiVersion": "v2",
"appData": "{}",
"mode": "vf"
}
The create response includes the source stream's id (you'll need it next), plus vfIngress and vfManifestUrl (see Step 2 and Step 4).
Create the overlay stream — same program, with kind: "filtergraph" and derivedFromId set to the source stream's id:
POST /program/api/v2/streams
Authorization: Bearer <serviceAccountJWT>
Content-Type: application/json
{
"streamName": "Overlay-My Stream",
"authKey": "my-overlay-auth-key",
"authType": "public",
"transcode": true,
"programID": "{programId}",
"programSlug": "my-stream",
"apiVersion": "v2",
"appData": "{}",
"mode": "vf",
"kind": "filtergraph",
"derivedFromId": "{sourceStreamId}"
}
Use the same programID/programSlug as the source, and the source stream's id as derivedFromId. The overlay stream is the one you'll play back to see the composited result.
You can look up the overlay sibling of any source stream directly:
GET /program/api/v2/streams?derivedFromId={sourceStreamId}
Returns the filtergraph stream(s) derived from that source, or an empty array [] (HTTP 200) if none exists.
Step 2 — Build the RTMP ingest URL
You ingest video into the source stream over RTMP. The ingest URL is a base URL plus a stream key.
Base URL — take the rtmp value from vfIngress in the stream response and trim it to the /origin path:
rtmp://{host}/origin
Stream key — the create-stream response returns a streamKey (e.g. live_stream-uuid?authKey=my-auth-key). Append the namespace parameter ns, which is your project ID:
{streamKey}&ns={projectId}
Enable the overlay — to tell the platform to composite the overlay and push the result to the overlay stream, append a URL-encoded appdata parameter pointing at the overlay stream:
{streamKey}&ns={projectId}&appdata={urlEncodedAppData}
where appdata is a URL-encoded JSON object:
{
"authKey": "{overlayStreamAuthKey}",
"overlay_tag": "{overlayStreamId}"
}
| Field | Description |
|---|---|
authKey | The authKey of the overlay stream. |
overlay_tag | The id of the overlay stream from Step 1. |
If the stream has no overlay, omit the appdata parameter entirely and you get a plain (non-composited) ingest.
function buildRtmpUrl({ vfIngressRtmp, streamKey, projectId, overlayStreamId, overlayAuthKey }) {
// Trim the vfIngress rtmp URL to its /origin base
const baseUrl = `${vfIngressRtmp.match(/^rtmp:\/\/[^/]+/)[0]}/origin`;
let key = `${streamKey}&ns=${projectId}`;
if (overlayStreamId && overlayAuthKey) {
const appdata = JSON.stringify({ authKey: overlayAuthKey, overlay_tag: overlayStreamId });
key += `&appdata=${encodeURIComponent(appdata)}`;
}
return { baseUrl, streamKey: key };
}
You can also ingest over WebRTC instead of RTMP — for example from the Aperture Live mobile app — by generating a WHIP link. The overlay is enabled the same way, by including appData with the overlay stream's authKey (as _authKey) and overlay_tag. See the Program API reference for the WHIP link endpoint.
Step 3 — Set the render config
The render config tells the platform what to render as the overlay. Configure it on the overlay stream, against your project's Media Assets host (returned as mediaAssetsHost from the dashboard public-config):
POST {mediaAssetsHost}/mediaassets/api/v1/render/config?ns={projectId}&tag={overlayStreamId}
Authorization: Bearer <serviceAccountJWT>
Content-Type: application/json
{
"renderType": "html2img",
"args": ["-u", "https://example.com/my-overlay"]
}
| Query param | Required | Description |
|---|---|---|
ns | Yes | Namespace — your project ID. |
tag | Yes | The overlay stream's id from Step 1. |
retention | No | How long to keep the config active, in seconds. Defaults to 30 days (2592000). |
| Body field | Required | Description |
|---|---|---|
renderType | Yes | Render plugin. Currently only "html2img" is supported. |
args | Yes | Command-line arguments to html2img. At minimum, -u followed by the target URL. |
html2img arguments
The args array is passed straight to the html2img renderer. -u (the target URL to capture) is required; the rest are optional:
| Flag | Default | Description |
|---|---|---|
-u, --url | — | Required. Target URL to capture. |
-W, --width | 1920 | Viewport width. |
-H, --height | 1080 | Viewport height. |
-i, --interval | 800 | Capture interval (ms). |
-c, --chroma | — | Chroma-key removal: auto, hex (#00B140), or RGB (0,177,64). |
-D, --dom-watch | off | Capture on DOM mutations (use with --selector). |
--resize | — | Resize output, e.g. 960x540 (or 960x0 to preserve aspect). |
-A, --animations | off | Enable CSS animations/transitions (off by default for consistent captures). |
POST {mediaAssetsHost}/mediaassets/api/v1/render/config?ns={projectId}&tag={overlayStreamId}&retention=15552000
Content-Type: application/json
{
"renderType": "html2img",
"args": ["-u", "https://example.com/my-overlay", "-W", "1280", "-H", "720", "-i", "500"]
}
Use GET (same URL) to read the current config and whether the render process is running (processRunning), and DELETE to remove it (stops the process if running; idempotent).
Step 4 — Play back the overlay
Play back the overlay stream — not the source — to see the composited result. Build its manifest URL from the overlay stream's id and vfManifestUrl:
https://{manifestHost}/manifest/api/v1/live/{overlayStreamId}.m3u8
where manifestHost is the host from the overlay stream's vfManifestUrl. Replace .m3u8 with .json for the JSON manifest. For video-foundation streams, always prefer vfIngress/vfManifestUrl over ingress/manifestUrl.
Putting it all together
- Create the streams — a program, a source stream, and an overlay stream (
kind: "filtergraph",derivedFromId= sourceid) under the same program. - Set the render config on the overlay stream (
renderType: "html2img",argswith at least-u <url>). - Start streaming — build the source stream's RTMP URL (Step 2) with the
appdataparameter pointing at the overlay, and start sending video. (Or generate a WHIP link for WebRTC ingest.) - Play back the overlay stream's manifest to watch the composited output.
Program API reference
The stream operations above are part of the v2 Program Service API — see the Program API reference. The render config endpoints live on the Media Assets host ({mediaAssetsHost}/mediaassets/api/v1/render/config). Every call requires a Bearer service-account JWT; the project is taken from the token's projectID claim.
- Overlay Streaming (Dashboard) — the no-code version of this flow.
- RTMP Push — fan the composited output out to YouTube.
- New to ingest? See Streaming Protocols.