Loading API documentation
Start Session
The client provides a convenient way to interact with the model API.
npm install @fal-ai/client@alpha @fal-ai/server-proxy@alphafal.realtime.open is currently available on the alpha release and may change in a minor version.
Keep FAL_KEY on your server and expose an authenticated proxy route to your browser. For a Next.js App Router application, create app/api/fal/proxy/route.ts:
import { route } from "@fal-ai/server-proxy/nextjs";
export const { GET, POST, PUT } = route;Set FAL_KEY in the server environment, and protect this route with your application's authentication before deploying it.
This deployment publishes a WMA WebRTC contract. The example uses fal.realtime.open; its media handlers, capture constraints, and control message are generated from the linked AsyncAPI document.
import { createFalClient } from "@fal-ai/client";
import { wma } from "@fal-ai/client/realtime";
const fal = createFalClient({ proxyUrl: "/api/fal/proxy" });
const session = fal.realtime.open(wma("minimax/h3-max/director"), {
receive: [
"video",
"audio"
],
onMedia: (stream) => {
const element = document.querySelector("video");
if (element) element.srcObject = stream;
},
onData: (raw) => {
const message = JSON.parse(raw);
if (message?.type === "configured" && message?.["prompt_version"] === 1) {
console.log("Session configured. You can now send text updates.");
}
console.log(message);
},
onState: (state) => console.log("realtime:", state),
onError: (error) => console.error(error),
});
// Send setup once. The client buffers this until the control channel opens.
// Wait for the matching acknowledgement in onData before sending updates.
session.send({
"prompt": "A continuous original live-action American sitcom produced in 1994, following the same ensemble of adult roommates, coworkers, neighbors, and rivals. Preserve appearances, apartment and workplace layouts, relationships, jobs, secrets, running jokes, and unresolved storylines. Advance through dialogue, entrances, misunderstandings, escalating attempts to hide mistakes, reversals, and warm character payoffs. Avoid references to existing sitcoms or actors.",
"type": "configure",
"protocol_version": 1,
"resolution": "768p",
"aspect_ratio": "16:9",
"prompt_version": 1,
"memory": 3
});
// Later, release the peer connection and network resources:
// await session.close();Configure once, wait for the model to acknowledge setup, then update the running session. The transport being live does not mean the model is configured.
Send configure once with /prompt_version set to 1. These field paths are JSON Pointers into message payloads. Use the setup message reference below for all configuration fields.
{
"prompt": "A continuous original live-action American sitcom produced in 1994, following the same ensemble of adult roommates, coworkers, neighbors, and rivals. Preserve appearances, apartment and workplace layouts, relationships, jobs, secrets, running jokes, and unresolved storylines. Advance through dialogue, entrances, misunderstandings, escalating attempts to hide mistakes, reversals, and warm character payoffs. Avoid references to existing sitcoms or actors.",
"type": "configure",
"protocol_version": 1,
"resolution": "768p",
"aspect_ratio": "16:9",
"prompt_version": 1,
"memory": 3
}Wait for configured whose /prompt_version matches the version you sent in setup. Only that acknowledgement enables text updates. Pending, applied, or rejected input events do not confirm session readiness.
Send prompt with your text at /prompt. Start from {"type":"prompt"}, add the text and the next /prompt_version, and validate the complete payload against the message schema. The first update uses 2; increase by 1 for each subsequent submission.
{
"type": "prompt",
"prompt": "They follow a narrow path down to the harbor.",
"prompt_version": 2
}This action sends only the template, text, and version fields. Other options in the raw message schema are outside this text-update flow. If you use them too, coordinate the same session-wide version sequence.
Match the version in each event to the submission it describes, including the initial setup input. Several updates can have different outcomes while the session stays ready.
prompt_pending): The model is preparing this input. Match the submitted version at /prompt_version.prompt_applied): Accepted for generation. The result may not be generating or visible yet. Match the submitted version at /prompt_version.prompt_rejected): This input was not accepted. An earlier applied input can remain active. Match the submitted version at /prompt_version.You can send another update while preparation is pending. Newer updates can replace older pending preparation, and the older input may never receive a final event. A missing acknowledgement means the outcome is unknown; it does not prove rejection.
For error, read the code at /code and the explanation at /error. Match input failures using /prompt_version.
configuration_timeout, initialization_timeout, invalid_initial_image, invalid_initial_audio, invalid_initial_script, invalid_input, balance_unavailable, content_policy, generation_timeout, generation_failed.stale_prompt_version.invalid_message, not_configured, immutable_settings.Treat unknown codes as diagnostics. An input failure without a known matching submission is also diagnostic; do not guess which input failed.
stream_exhausted ends this session attempt. A closed or failed connection also ends the attempt. Later messages must not reopen it. A session failure does not need to emit a separate end event.
Do not automatically replay setup or updates. Start a new sequence at 1 for a new session. Gaps are allowed; never reuse a version after uncertain delivery, and keep versions within the message schema and JavaScript's safe integer range.
The browser connects through your server proxy, which reads FAL_KEY from the server environment. Never put that key in browser code.
When running code on the client-side (e.g. in a browser, mobile app or GUI applications), make sure to not expose your FAL_KEY. Instead, use a server-side proxy to make requests to the API. For more information, check out our server-side integration guide.
Client contract for the WebRTC session created by the linked OpenAPI operation.
Tracks are described from the browser's perspective. Send tracks are captured by the browser and sent to the model; receive tracks come back from the model.
No send media tracks.
{
"frameRate": 24
}type: "configure"payload: objectCorrelation field: /prompt_version
Additional properties: not allowed
promptstring* requiredAt least 1 character · At most 50000 characters
resolutionstringDefault: "768p"
Values: "480p", "768p", "1080p"
aspect_ratiostringDefault: "16:9"
Values: "16:9", "9:16", "1:1"
image_urlstring | nullURL of the image to use as the exact first frame. The opening prompt expansion also sees this image, so the first segment's prompt is grounded in it.
Default: null
At least 1 character when not null
end_image_urlstring | nullOne-shot exact final frame for the first chunk. Director jointly plans that arrival and the following checkpoint continuation.
Default: null
At least 1 character when not null
audio_urlstring | nullOptional startup soundtrack. The stream's audio is pinned to this recording from the first chunk as FL2VA target audio, not a Ref2VA reference: every chunk is conditioned on the next window of it (plus the regenerated seam) until it ends, and the source PCM itself is what plays. Live prompt messages can replace or queue more audio at any time.
Default: null
At least 1 character when not null
memoryintegerNumber of prior segment prompts retained as context for future prompt expansion.
Default: 12
minimum: 1 · maximum: 50
audio_bitrateinteger | nullSession audio target in bits/s: 96000, 128000, or 192000. Explicit values use Opus audio mode on direct WebRTC; LiveKit uses its native mode with the same bitrate target. Null preserves transport defaults (WebRTC: 96000, voip). Immutable; reconnect to compare.
Default: null
When not null:
Values: 96000, 128000, 192000
seedinteger | nullDefault: null
typestring* requiredConstant: "configure"
protocol_versionintegerConstant: 1
prompt_versioninteger* requiredminimum: 1
scriptobject[] | nullOptional upfront script: beats at whole-second offsets from the first generated video. prompt stays the series premise; a beat prompt directs from its offset on. Cannot be combined with end_image_url or audio_url (place them in the script).
Default: null
Must match at least one of the following:
object[]minItems: 1 · maxItems: 64
objectA direction on the associated video's clock, never the stream clock.
Additional properties: not allowed
promptstring | nullDirection that starts at this offset and persists until the next text beat; it also appears as a timed span inside the chunk.
Default: null
At least 1 character when not null · At most 50000 characters when not null
audio_urlstring | nullAudio that starts playing exactly at this offset as FL2VA target audio; overlapping sources are mixed. The source PCM itself plays.
Default: null
At least 1 character when not null
offsetinteger* requiredWhole seconds from the start of the first video generated under this script. Text and audio placed here start at this second; an end image here is the exact final frame at this second.
minimum: 0
end_image_urlstring | nullExact final frame of the chunk that ends at this offset. Offsets of successive end images must be at least three seconds apart.
Default: null
At least 1 character when not null
nullDrop a fixed ensemble into audience-created misunderstandings.
{
"prompt": "A continuous original live-action American sitcom produced in 1994, following the same ensemble of adult roommates, coworkers, neighbors, and rivals. Preserve appearances, apartment and workplace layouts, relationships, jobs, secrets, running jokes, and unresolved storylines. Advance through dialogue, entrances, misunderstandings, escalating attempts to hide mistakes, reversals, and warm character payoffs. Avoid references to existing sitcoms or actors.",
"type": "configure",
"protocol_version": 1,
"resolution": "768p",
"aspect_ratio": "16:9",
"prompt_version": 1,
"memory": 3
}type: "ping"payload: objectAdditional properties: not allowed
typestring* requiredConstant: "ping"
tsnumber* required{
"type": "ping",
"ts": 0
}type: "prompt"payload: objectCorrelation field: /prompt_version
Additional properties: not allowed
promptstring | nullDefault: null
At least 1 character when not null · At most 50000 characters when not null
end_image_urlstring | nullDefault: null
At least 1 character when not null
audio_urlstring | nullFL2VA target audio for future chunks; this is not a Ref2VA reference. With audio_behavior 'replace' (default) it starts at the next undispatched chunk and drops any queued audio; with 'queue' it plays after every previously accepted source ends, sample-exact.
Default: null
At least 1 character when not null
audio_behaviorstring'replace' cuts to this audio at the next chunk; 'queue' appends it.
Default: "replace"
Values: "replace", "queue"
replanbooleanWhen a prompt is included: true (default) busts the planned prompt queue so the new direction applies at the next undispatched chunk; false appends the direction after the already-planned chunks.
Default: true
typestring* requiredConstant: "prompt"
script_modestring'replace' cuts to the script at the next chunk; 'append' queues it.
Default: "replace"
Values: "replace", "append"
scriptobject[] | nullA new script: beats at whole-second offsets from the first video generated under it. Exclusive with prompt/end_image_url/audio_url. With script_mode 'replace' (default) it replaces the pending and current direction at the next undispatched chunk; 'append' starts at the chunk boundary after the running script's last text beat is represented and its audio tails have finished (queued behind earlier appended scripts). Beats without a prompt keep the current text direction.
Default: null
Must match at least one of the following:
object[]minItems: 1 · maxItems: 64
objectA direction on the associated video's clock, never the stream clock.
Additional properties: not allowed
promptstring | nullDirection that starts at this offset and persists until the next text beat; it also appears as a timed span inside the chunk.
Default: null
At least 1 character when not null · At most 50000 characters when not null
audio_urlstring | nullAudio that starts playing exactly at this offset as FL2VA target audio; overlapping sources are mixed. The source PCM itself plays.
Default: null
At least 1 character when not null
offsetinteger* requiredWhole seconds from the start of the first video generated under this script. Text and audio placed here start at this second; an end image here is the exact final frame at this second.
minimum: 0
end_image_urlstring | nullExact final frame of the chunk that ends at this offset. Offsets of successive end images must be at least three seconds apart.
Default: null
At least 1 character when not null
nullprompt_versioninteger* requiredminimum: 1
Send a new direction after configuration is acknowledged
{
"script_mode": "replace",
"type": "prompt",
"prompt": "They follow a narrow path down to the harbor.",
"audio_behavior": "replace",
"prompt_version": 2,
"replan": true
}type: "stop"payload: objectAdditional properties: not allowed
typestring* requiredConstant: "stop"
{
"type": "stop"
}type: "audio_applied"payload: objectAn audio update was accepted; it conditions the next eligible chunk.
starts_at_chunk_index is the first chunk that can carry it (the next
undispatched chunk for replace; for queue the chunk where the
preceding sources end, if already known).
remaining_secondsnumber* requiredminimum: 0
typestring* requiredConstant: "audio_applied"
duration_secondsnumber* requiredexclusiveMinimum: 0
queued_sourcesinteger* requiredminimum: 0
transcribedboolean* requiredbehaviorstring* requiredValues: "replace", "queue"
prompt_versioninteger* requiredminimum: 1
sourcestring* required{
"remaining_seconds": 0,
"type": "audio_applied",
"duration_seconds": 5e-324,
"queued_sources": 0,
"transcribed": true,
"behavior": "replace",
"prompt_version": 1,
"source": "string"
}type: "audio_exhausted"payload: objectAccepted audio ran out inside this chunk; the rest is silence.
Later chunks continue without audio conditioning (checkpoint continuation on the same family) until new audio is accepted.
chunk_indexinteger* requiredminimum: 0
typestring* requiredConstant: "audio_exhausted"
silent_secondsnumber* requiredminimum: 0
source_versioninteger | nullDefault: null
minimum: 0 when not null
{
"chunk_index": 0,
"type": "audio_exhausted",
"silent_seconds": 0
}type: "audio_pending"payload: objecttypestring* requiredConstant: "audio_pending"
prompt_versioninteger* requiredminimum: 1
behaviorstring* requiredValues: "replace", "queue"
{
"type": "audio_pending",
"prompt_version": 1,
"behavior": "replace"
}type: "audio_rejected"payload: objecttypestring* requiredConstant: "audio_rejected"
errorstring* requiredreasonstring* requiredValues: "invalid_audio", "content_policy", "preparation_failed", "queue_full", "stale_prompt_version"
prompt_versioninteger* requiredminimum: 1
{
"type": "audio_rejected",
"error": "string",
"reason": "invalid_audio",
"prompt_version": 1
}type: "chunk"payload: objectscheduling_lead_msnumber* requiredtypestring* requiredConstant: "chunk"
script_end_keyframeboolean | nullDefault: null
requested_duration_secondsinteger* requiredminimum: 5 · maximum: 15
buffer_depth_chunksinteger* requiredminimum: 0
dispatchobject* requiredwall_msnumber* requiredminimum: 0
overhead_msnumber* requiredminimum: 0
phases_msobject* requiredAdditional properties: number
numberclassified_msnumber* requiredminimum: 0
prompt_versioninteger* requiredminimum: 1
next_generation_estimate_secondsnumber* requiredminimum: 0
presented_frame_countinteger | nullDefault: null
exclusiveMinimum: 0 when not null
trimmed_context_framesinteger* requiredminimum: 0
hard_cutbooleanDefault: false
native_playable_frame_countinteger | nullDefault: null
exclusiveMinimum: 0 when not null
script_versioninteger | nullDefault: null
minimum: 1 when not null
routestring* requiredValues: "gorgonea", "betelgeuse", "regulus", "unknown"
scheduling_slack_msnumber* requiredbuffer_depth_secondsnumber* requiredminimum: 0
playback_secondsnumber* requiredexclusiveMinimum: 0
generated_frame_countinteger* requiredexclusiveMinimum: 0
script_offset_secondsinteger | nullDefault: null
minimum: 0 when not null
generation_secondsnumber* requiredminimum: 0
chunk_indexinteger* requiredminimum: 0
{
"scheduling_lead_ms": 0,
"type": "chunk",
"requested_duration_seconds": 5,
"buffer_depth_chunks": 0,
"dispatch": {
"wall_ms": 0,
"overhead_ms": 0,
"phases_ms": {},
"classified_ms": 0
},
"prompt_version": 1,
"next_generation_estimate_seconds": 0,
"trimmed_context_frames": 0,
"route": "gorgonea",
"scheduling_slack_ms": 0,
"buffer_depth_seconds": 0,
"playback_seconds": 5e-324,
"generated_frame_count": 1,
"generation_seconds": 0,
"chunk_index": 0
}type: "chunk_metrics"payload: objectchunk_consumable_ready_msnumber | nullDefault: null
minimum: 0 when not null
typestring* requiredConstant: "chunk_metrics"
routestring* requiredValues: "gorgonea", "betelgeuse", "regulus", "unknown"
unitsstring* requiredConstant: "ms"
chunk_consumable_interval_msnumber | nullDefault: null
minimum: 0 when not null
phases_msobject* requiredAdditional properties: number
numbergaugesobject* requiredAdditional properties: number
numberchunk_indexinteger* requiredminimum: 0
{
"type": "chunk_metrics",
"route": "gorgonea",
"units": "ms",
"phases_ms": {},
"gauges": {},
"chunk_index": 0
}type: "configured"payload: objectCorrelation field: /prompt_version
has_initial_imageboolean | nullDefault: null
typestring* requiredConstant: "configured"
enable_safety_checkerboolean* requiredchunk_durationinteger | nullDefault: null
minimum: 5 when not null · maximum: 15 when not null
memoryinteger | nullDefault: null
minimum: 1 when not null · maximum: 50 when not null
prompt_versioninteger* requiredminimum: 1
resolutionstring | string | nullDefault: null
Must match at least one of the following:
stringValues: "480p", "544p", "640p", "704p", "768p"
stringValues: "480p", "768p", "1080p"
nullaudio_bitrateinteger | nullDefault: null
When not null:
Values: 96000, 128000, 192000
aspect_ratiostring | nullDefault: null
When not null:
Values: "16:9", "9:16", "1:1"
has_initial_audioboolean | nullDefault: null
accelerationstring | nullDefault: null
When not null:
Values: "none", "regular"
{
"type": "configured",
"enable_safety_checker": true,
"prompt_version": 1
}type: "deadline_missed"payload: objecttypestring* requiredConstant: "deadline_missed"
late_by_secondsnumber* requiredminimum: 0
behaviorstring* requiredConstant: "freeze_video_and_silence_audio_until_ready"
chunk_indexinteger* requiredminimum: 0
{
"type": "deadline_missed",
"late_by_seconds": 0,
"behavior": "freeze_video_and_silence_audio_until_ready",
"chunk_index": 0
}type: "error"payload: objectCorrelation field: /prompt_version
typestring* requiredConstant: "error"
codestring* requiredValues: "balance_unavailable", "content_policy", "configuration_timeout", "generation_failed", "generation_timeout", "immutable_settings", "initialization_timeout", "invalid_initial_image", "invalid_initial_audio", "invalid_initial_script", "invalid_input", "invalid_message", "not_configured", "stale_prompt_version"
errorstring* requiredprompt_versioninteger | nullDefault: null
minimum: 1 when not null
detailobject[] | nullDefault: null
Must match at least one of the following:
object[]objectnull{
"type": "error",
"code": "balance_unavailable",
"error": "string"
}type: "pong"payload: objecttypestring* requiredConstant: "pong"
client_tsnumber* required{
"type": "pong",
"client_ts": 0
}type: "prompt_applied"payload: objectCorrelation field: /prompt_version
A direction (or script) is admitted; the script fields report scripts.
script_origin_chunk_index is the first chunk generated under the
script when that is already fixed (a replacement binds to the next
undispatched chunk); an appended script reports None until it starts.
script_modestring | nullDefault: null
When not null:
Values: "replace", "append"
typestring* requiredConstant: "prompt_applied"
script_queuedinteger | nullDefault: null
minimum: 0 when not null
script_origin_chunk_indexinteger | nullDefault: null
minimum: 0 when not null
script_beatsinteger | nullDefault: null
minimum: 1 when not null
prompt_versioninteger* requiredminimum: 1
{
"type": "prompt_applied",
"prompt_version": 1
}type: "prompt_pending"payload: objectCorrelation field: /prompt_version
typestring* requiredConstant: "prompt_pending"
prompt_versioninteger* requiredminimum: 1
{
"type": "prompt_pending",
"prompt_version": 1
}type: "prompt_rejected"payload: objectCorrelation field: /prompt_version
typestring* requiredConstant: "prompt_rejected"
errorstring | nullDefault: null
reasonstring* requiredValues: "content_policy", "preparation_failed", "stale_prompt_version", "invalid_script", "infeasible_timing", "invalid_audio", "invalid_image", "queue_full"
prompt_versioninteger* requiredminimum: 1
{
"type": "prompt_rejected",
"reason": "content_policy",
"prompt_version": 1
}type: "session_info"payload: objectaudio_conditioningbooleanConstant: true
typestring* requiredConstant: "session_info"
script_max_decoded_audio_bytesintegerDefault: 67108864
max_audio_source_secondsnumberDefault: 600
script_max_audio_beatsintegerConstant: 8
resolutionsstring[]Default: ["480p","768p","1080p"]
stringValues: "480p", "768p", "1080p"
continuation_context_framesintegerConstant: 39
min_chunk_durationintegerConstant: 5
default_memoryintegerConstant: 12
min_memoryintegerConstant: 1
script_modesstring[]Default: ["replace","append"]
stringValues: "replace", "append"
max_memoryintegerConstant: 50
aspect_ratiosstring[]Default: ["16:9","9:16","1:1"]
stringValues: "16:9", "9:16", "1:1"
appstringConstant: "minimax-h3-max-director"
audio_sample_rateintegerConstant: 48000
script_max_beatsintegerConstant: 64
opening_reservedbooleanDefault: false
script_max_pendingintegerConstant: 4
script_min_end_image_spacing_secondsintegerConstant: 3
audio_bitratesinteger[]Default: [96000,128000,192000]
integerValues: 96000, 128000, 192000
protocol_versionintegerConstant: 1
default_audio_bitrateinteger | nullDefault: null
When not null:
Values: 96000, 128000, 192000
accelerationsstring[]Default: ["none","regular"]
stringValues: "none", "regular"
default_accelerationstringConstant: "regular"
script_min_chunk_secondsintegerConstant: 3
script_min_opening_chunk_secondsintegerConstant: 5
backend_selectionstringConstant: "minimax-h3-turbo-balancer"
conditioning_audio_sample_rateintegerConstant: 32000
prompt_expanderstringConstant: "fast"
max_session_secondsnumber | nullDefault: null
session_limit_scopestringDefault: "configured"
Values: "configured", "effective"
scriptsbooleanConstant: true
client_message_typesstring[]Default: ["configure","ping","prompt","stop"]
stringValues: "configure", "ping", "prompt", "stop"
prompt_context_segmentsintegerConstant: 12
script_session_max_decoded_audio_bytesintegerDefault: 335544320
max_chunk_durationintegerConstant: 15
controller_machine_typestringConstant: "XL"
script_max_queuedintegerConstant: 4
script_max_end_imagesintegerConstant: 16
one_session_per_machinebooleanConstant: true
default_chunk_durationintegerConstant: 10
prompt_deck_sizeintegerConstant: 6
continuation_playback_secondsnumberDefault: 8.5
fpsintegerConstant: 24
server_message_typesstring[]Default: ["audio_applied","audio_exhausted","audio_pending","audio_rejected","chunk","chunk_metrics","configured","deadline_missed","error","pong","prompt_applied","prompt_pending","prompt_rejected","session_info","session_metrics","stream_exhausted"]
stringValues: "audio_applied", "audio_exhausted", "audio_pending", "audio_rejected", "chunk", "chunk_metrics", "configured", "deadline_missed", "error", "pong", "prompt_applied", "prompt_pending", "prompt_rejected", "session_info", "session_metrics", "stream_exhausted"
audio_behaviorsstring[]Default: ["replace","queue"]
stringValues: "replace", "queue"
chunk_secondsintegerConstant: 10
{
"audio_conditioning": true,
"type": "session_info",
"script_max_audio_beats": 8,
"continuation_context_frames": 39,
"min_chunk_duration": 5,
"default_memory": 12,
"min_memory": 1,
"max_memory": 50,
"app": "minimax-h3-max-director",
"audio_sample_rate": 48000,
"script_max_beats": 64,
"script_max_pending": 4,
"script_min_end_image_spacing_seconds": 3,
"protocol_version": 1,
"default_acceleration": "regular",
"script_min_chunk_seconds": 3,
"script_min_opening_chunk_seconds": 5,
"backend_selection": "minimax-h3-turbo-balancer",
"conditioning_audio_sample_rate": 32000,
"prompt_expander": "fast",
"scripts": true,
"prompt_context_segments": 12,
"max_chunk_duration": 15,
"controller_machine_type": "XL",
"script_max_queued": 4,
"script_max_end_images": 16,
"one_session_per_machine": true,
"default_chunk_duration": 10,
"prompt_deck_size": 6,
"fps": 24,
"chunk_seconds": 10
}type: "session_metrics"payload: objectfinalbooleanDefault: false
typestring* requiredConstant: "session_metrics"
phasesobject* requiredAdditional properties: object
objectp50_msnumber* requiredminimum: 0
total_msnumber* requiredminimum: 0
max_msnumber* requiredminimum: 0
countinteger* requiredminimum: 0
p95_msnumber* requiredminimum: 0
unitsstring* requiredConstant: "ms"
history_limitinteger* requiredminimum: 1
gaugesobject* requiredAdditional properties: number
numbersession_wall_msnumber* requiredminimum: 0
history_sizeinteger* requiredminimum: 0
{
"type": "session_metrics",
"phases": {},
"units": "ms",
"history_limit": 1,
"gauges": {},
"session_wall_ms": 0,
"history_size": 0
}type: "stream_exhausted"payload: objecttypestring* requiredConstant: "stream_exhausted"
chunksinteger* requiredminimum: 0
reasonstring* requiredValues: "stopped", "session_limit"
{
"type": "stream_exhausted",
"chunks": 0,
"reason": "stopped"
}