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("vast/eden-live-14b-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.
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": 16,
"height": 480,
"width": 832
}type: "action"payload: objectAdditional properties: not allowed
A game UI action, e.g. a "Play again" or "Pause" button.
typestring* requiredConstant: "action"
actionstring* requiredOne of the game's actions from session_info (restart, pause).
At least 1 character · At most 32 characters
{
"type": "action",
"action": "restart"
}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: objectAdditional properties: not allowed
Pick 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,
"game": "freeze",
"condition_capture": "output",
"dynamic_prompt": true,
"type": "configure",
"hud": "baked"
}type: "input"payload: objectAdditional properties: not allowed
Ordered 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.
Additional properties: not allowed
xnumber | nullDefault: null
minimum: 0 when not null · maximum: 1 when not null
namestring* requiredAt least 1 character · At most 32 characters
ynumber | nullDefault: null
minimum: 0 when not null · maximum: 1 when not null
kindstringDefault: "key"
Values: "key", "action", "pointer"
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.
activatedstring[]maxItems: 16
stringValues: "W", "A", "S", "D", "ARROWUP", "ARROWDOWN", "ARROWLEFT", "ARROWRIGHT", "F", " ", "E", "SHIFT", "R", "G", "P", "ESCAPE"
pressedstring[]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: objectAdditional properties: not allowed
Change 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: objectAdditional properties: not allowed
Restart 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
condition_capturestring | nullDefault: null
When not null:
Values: "output", "encoder", "encoder_half"
typestring* requiredConstant: "reset"
hudstring | nullDefault: null
When not null:
Values: "baked", "client", "none"
autopilotboolean | nullDefault: null
dynamic_promptboolean | nullDefault: null
Freeze stepping stones across a lava lake and reach the far platform.
{
"game": "freeze",
"type": "reset"
}type: "stop"payload: objectAdditional properties: not allowed
End the session.
typestring* requiredConstant: "stop"
{
"type": "stop"
}type: "configured"payload: objectgameobject | nullDefault: null
Must match at least one of the following:
objectpromptstring* requiredthumbnail_urlstring | nullDefault: null
descriptionstring* requiredtitlestring* requiredinstructionsstring[]* requiredstringcontrolsobject[]* requiredobjectkeystring* requiredcommandstring | nullSemantic command name.
Default: null
labelstring* requiredholdbooleanDefault: true
actionsstring[]* requiredstringidstring* 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.
controlsobject[]* requiredobjectkeystring* requiredcommandstring | nullSemantic command name.
Default: null
labelstring* requiredholdbooleanDefault: true
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"
typestring* requiredConstant: "controls_state"
{
"controls": [],
"type": "controls_state"
}type: "error"payload: objectretryableboolean | nullDefault: null
typestring* requiredConstant: "error"
errorstring* required{
"type": "error",
"error": "string"
}type: "game_state"payload: objectGame state at an output frame; frames are numbered from session start.
frameinteger* requiredroundinteger* requiredtypestring* requiredConstant: "game_state"
stateobject | null* requiredMust match at least one of the following:
objectnull{
"frame": 0,
"round": 0,
"type": "game_state",
"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.
client_tsnumber | nullDefault: null
server_tsnumber | nullDefault: null
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:
objectpromptstring* requiredthumbnail_urlstring | nullDefault: null
descriptionstring* requiredtitlestring* requiredinstructionsstring[]* requiredstringcontrolsobject[]* requiredobjectkeystring* requiredcommandstring | nullSemantic command name.
Default: null
labelstring* requiredholdbooleanDefault: true
actionsstring[]* requiredstringidstring* requirednulltypestring* requiredConstant: "reset_applied"
{
"type": "reset_applied"
}type: "session_info"payload: objectinfoobject* requiredsessionstring* requiredValues: "video", "game"
typestring* requiredConstant: "session_info"
{
"info": {},
"session": "video",
"type": "session_info"
}type: "stats"payload: objectprompt_msnumber | nullPer-chunk prompt update time with dynamic_prompt, unchanged chunks included (moving average).
Default: null
chunkinteger* requiredpipelinedboolean | nullThe next chunk renders while this one generates (throughput mode).
Default: null
render_msnumber | nullGame render time per chunk (moving average).
Default: null
realtime_factornumber* requiredGenerated frames per second / target fps.
generate_msnumber | nullModel time per chunk (moving average).
Default: null
latency_msnumber | nullInput or control capture to frame handoff, median.
Default: null
chunk_msnumber* requiredtypestring* requiredConstant: "stats"
input_latency_msnumber | nullGame control message arrival to the handoff of the first frame it changes, median.
Default: null
loop_lag_msnumber | nullServer event-loop lag, p95 over the stats window.
Default: null
generation_fpsnumber* requiredqueue_framesinteger* requiredbillable_unitsnumber | nullSeconds of video billed so far this session (queued for playback); the final total settles when the session closes.
Default: null
{
"chunk": 0,
"realtime_factor": 0,
"chunk_ms": 0,
"type": "stats",
"generation_fps": 0,
"queue_frames": 0
}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"
}