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@alphaKeep 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("vast/eden-live-5b-game"), {
receive: [
"video"
],
onMedia: (stream) => {
const element = document.querySelector("video");
if (element) element.srcObject = stream;
},
onData: (raw) => {
const message = JSON.parse(raw);
console.log(message);
},
onState: (state) => console.log("realtime:", state),
onError: (error) => console.error(error),
});
// Consult the session flow and message reference before sending control messages.
// Later, release the peer connection and network resources:
// await session.close();This world starts automatically. Wait for the first video frame before using the movement controls.
Send commands with held controls in active and newly pressed controls in activated. Send empty arrays when controls are released or focus leaves the world.
configure and wait for configured before sending another world update. Setup is optional and can be used once per session.reset and wait for reset_applied before sending another world update.prompt and wait for prompt_applied before sending another world update.World updates run one at a time and must not be replayed automatically. A error event is diagnostic and does not identify a failed update; read /error for details. The session ends on stream_exhausted.
The browser connects through your server proxy, which reads FAL_KEY from the server environment. Never put that key in browser code.
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.
{
"width": 1280,
"frameRate": 24,
"height": 704
}type: "action"payload: objectA game UI action, e.g. a "Play again" or "Pause" button.
actionstring* requiredOne of the game's actions from session_info (restart, pause).
At least 1 character · At most 32 characters
typestring* requiredConstant: "action"
{
"action": "restart",
"type": "action"
}type: "commands"payload: objectSemantic command-state report.
active contains commands currently held by the user and activated
contains edge-triggered commands since the previous report. A browser may
map a keyboard, gamepad, touch control, or agent action to these names.
activestring[]maxItems: 14
stringValues: "forward", "backward", "left", "right", "look_up", "look_down", "look_left", "look_right", "fire", "jump", "ability_1", "ability_2", "ability_3", "ability_4"
activatedstring[]maxItems: 14
stringValues: "forward", "backward", "left", "right", "look_up", "look_down", "look_left", "look_right", "fire", "jump", "ability_1", "ability_2", "ability_3", "ability_4"
typestring* requiredConstant: "commands"
{
"active": [
"forward",
"fire"
],
"activated": [],
"type": "commands"
}type: "configure"payload: objectPick the game and how it is presented, once per connection; await configured.
gamestring | nullGame id from /game/info. Defaults to the first game.
Default: null
When not null:
Values: "freeze", "spiders", "pulse", "deepwater", "kaiju", "arcadeduel", "desert", "snowball", "puppy", "cat", "drone", "herding"
hudstring'baked' draws the game's HUD into the video; 'client' only sends game_state messages so the client draws its own UI; 'none' sends neither.
Default: "baked"
Values: "baked", "client", "none"
autopilotbooleanLet the game play itself; held player input overrides it.
Default: false
promptstring | nullOverride the game's built-in scene description.
Default: null
At most 2000 characters when not null
image_urlstring | nullOverride the game's photoreal opening frame.
Default: null
At most 60000 characters when not null
seedinteger | nullDefault: null
minimum: 0 when not null · maximum: 2147483647 when not null
condition_capturestringResolution the game frames the model conditions on are captured at: 'output', 'encoder' (the control encoder's input, which every capture is resized to) or 'encoder_half'. Smaller captures render faster; sizes are in /game/info.
Default: "output"
Values: "output", "encoder", "encoder_half"
dynamic_promptbooleanBefore each chunk, re-encode the prompt as the scene description plus the game's description of what happened in that chunk (shots, hits, turns). Adds the text-encode time to input latency on chunks whose description changed.
Default: true
typestring* requiredConstant: "configure"
Freeze stepping stones across a lava lake and reach the far platform.
{
"autopilot": false,
"condition_capture": "output",
"game": "freeze",
"hud": "baked",
"dynamic_prompt": true,
"type": "configure"
}type: "input"payload: objectOrdered raw input events for custom game clients.
Clicks on the baked HUD arrive as pointer presses (see MessageUI).
eventsobject[]* requiredminItems: 1 · maxItems: 64
objectOne raw input event; see GameController.push_input.
xnumber | nullDefault: null
minimum: 0 when not null · maximum: 1 when not null
ynumber | nullDefault: null
minimum: 0 when not null · maximum: 1 when not null
kindstringDefault: "key"
Values: "key", "action", "pointer"
namestring* requiredAt least 1 character · At most 32 characters
phasestringDefault: "press"
Values: "down", "up", "press"
typestring* requiredConstant: "input"
{
"events": [
{
"name": "string"
}
],
"type": "input"
}type: "keys"payload: objectLegacy physical-key report retained for existing WMA clients.
pressedstring[]maxItems: 16
stringValues: "W", "A", "S", "D", "ARROWUP", "ARROWDOWN", "ARROWLEFT", "ARROWRIGHT", "F", " ", "E", "SHIFT", "R", "G", "P", "ESCAPE"
activatedstring[]maxItems: 16
stringValues: "W", "A", "S", "D", "ARROWUP", "ARROWDOWN", "ARROWLEFT", "ARROWRIGHT", "F", " ", "E", "SHIFT", "R", "G", "P", "ESCAPE"
typestring* requiredConstant: "keys"
{
"type": "keys"
}type: "ping"payload: objectLatency probe; the server echoes ts back in a pong payload.
tsnumber | nullDefault: null
typestring* requiredConstant: "ping"
{
"type": "ping"
}type: "prompt"payload: objectChange the scene description; applied at the next chunk boundary.
promptstring* requiredText the model renders the scene with. Keeps the current world.
At least 1 character · At most 2000 characters
typestring* requiredConstant: "prompt"
{
"prompt": "string",
"type": "prompt"
}type: "reset"payload: objectRestart the round (and the model), optionally switching game or settings.
gamestring | nullGame id from /game/info. Defaults to the first game.
Default: null
When not null:
Values: "freeze", "spiders", "pulse", "deepwater", "kaiju", "arcadeduel", "desert", "snowball", "puppy", "cat", "drone", "herding"
promptstring | nullOverride the game's built-in scene description.
Default: null
At most 2000 characters when not null
image_urlstring | nullOverride the game's photoreal opening frame.
Default: null
At most 60000 characters when not null
seedinteger | nullDefault: null
minimum: 0 when not null · maximum: 2147483647 when not null
dynamic_promptboolean | nullDefault: null
condition_capturestring | nullDefault: null
When not null:
Values: "output", "encoder", "encoder_half"
hudstring | nullDefault: null
When not null:
Values: "baked", "client", "none"
autopilotboolean | nullDefault: null
typestring* requiredConstant: "reset"
Freeze stepping stones across a lava lake and reach the far platform.
{
"game": "freeze",
"type": "reset"
}type: "stop"payload: objectEnd the session.
typestring* requiredConstant: "stop"
{
"type": "stop"
}type: "configured"payload: objectgameobject | nullDefault: null
Must match at least one of the following:
objectactionsstring[]* requiredstringthumbnail_urlstring | nullDefault: null
descriptionstring* requiredinstructionsstring[]* requiredstringpromptstring* requiredidstring* requiredcontrolsobject[]* requiredobjectlabelstring* requiredholdbooleanDefault: true
keystring* requiredcommandstring | nullSemantic command name.
Default: null
titlestring* requirednulltypestring* requiredConstant: "configured"
{
"type": "configured"
}type: "controls_state"payload: objectThe current game's complete control set, sent after configured and
reset_applied and whenever the game changes its controls mid-round.
Only controls with a command can be pressed with commands; clients
show and bind those, and drop the rest of the vocabulary.
pointerstring | nullMouse input the client should offer: 'click' presses buttons the baked HUD draws; absent offers none.
Default: null
When not null:
Values: "click", "capture"
controlsobject[]* requiredobjectlabelstring* requiredholdbooleanDefault: true
keystring* requiredcommandstring | nullSemantic command name.
Default: null
typestring* requiredConstant: "controls_state"
{
"controls": [],
"type": "controls_state"
}type: "error"payload: objectretryableboolean | nullDefault: null
errorstring* requiredtypestring* requiredConstant: "error"
{
"error": "string",
"type": "error"
}type: "game_state"payload: objectGame state at an output frame; frames are numbered from session start.
frameinteger* requiredstateobject | null* requiredMust match at least one of the following:
objectnullroundinteger* requiredtypestring* requiredConstant: "game_state"
{
"frame": 0,
"state": {},
"round": 0,
"type": "game_state"
}type: "pong"payload: objectReply to a ping, echoing the client's timestamp for RTT measurement.
The echoed timestamp has two wire spellings: apps that handle ping
themselves echo ts; the fallback handler in
:class:registry.wma.sdk.Session replies with client_ts. Clients read
whichever is present. server_ts is the (unsynchronized) runner clock; it
only shows how the round trip divides once the client has an offset estimate.
tsnumber | nullDefault: null
server_tsnumber | nullDefault: null
client_tsnumber | nullDefault: null
typestring* requiredConstant: "pong"
{
"type": "pong"
}type: "prompt_applied"payload: objecttypestring* requiredConstant: "prompt_applied"
{
"type": "prompt_applied"
}type: "reset_applied"payload: objectgameobject | nullDefault: null
Must match at least one of the following:
objectactionsstring[]* requiredstringthumbnail_urlstring | nullDefault: null
descriptionstring* requiredinstructionsstring[]* requiredstringpromptstring* requiredidstring* requiredcontrolsobject[]* requiredobjectlabelstring* requiredholdbooleanDefault: true
keystring* requiredcommandstring | nullSemantic command name.
Default: null
titlestring* requirednulltypestring* requiredConstant: "reset_applied"
{
"type": "reset_applied"
}type: "session_info"payload: objectsessionstring* requiredValues: "video", "game"
infoobject* requiredtypestring* requiredConstant: "session_info"
{
"session": "video",
"info": {},
"type": "session_info"
}type: "stats"payload: objectrealtime_factornumber* requiredGenerated frames per second / target fps.
generation_fpsnumber* requiredqueue_framesinteger* requiredchunkinteger* requiredpipelinedboolean | nullThe next chunk renders while this one generates (throughput mode).
Default: null
generate_msnumber | nullModel time per chunk (moving average).
Default: null
render_msnumber | nullGame render time per chunk (moving average).
Default: null
input_latency_msnumber | nullGame control message arrival to the handoff of the first frame it changes, median.
Default: null
chunk_msnumber* requiredlatency_msnumber | nullInput or control capture to frame handoff, median.
Default: null
loop_lag_msnumber | nullServer event-loop lag, p95 over the stats window.
Default: null
prompt_msnumber | nullPer-chunk prompt update time with dynamic_prompt, unchanged chunks included (moving average).
Default: null
billable_unitsnumber | nullSeconds of video billed so far this session (queued for playback); the final total settles when the session closes.
Default: null
typestring* requiredConstant: "stats"
{
"realtime_factor": 0,
"generation_fps": 0,
"queue_frames": 0,
"chunk": 0,
"chunk_ms": 0,
"type": "stats"
}type: "stream_exhausted"payload: objectThe model has no more frames to send and the current peer is closing. Clients must negotiate a new session to continue.
typestring* requiredConstant: "stream_exhausted"
{
"type": "stream_exhausted"
}