maxItems: 8
About
Start Session
1. Calling the API#
Install the client#
The client provides a convenient way to interact with the model API.
npm install @fal-ai/client@alpha @fal-ai/server-proxy@alphaExperimental realtime API
fal.realtime.open is currently available on the alpha release and may change in a minor version.
Set up the server proxy#
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.
Real-time via WebRTC#
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("fal-ai/abot-world-0"), {
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();Session flow#
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.
- Send
configureand wait forconfiguredbefore sending another world update. Setup is optional and can be used once per session. - Send
resetand wait forreset_appliedbefore sending another world update. - Send
promptand wait forprompt_appliedbefore 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.
2. Authentication#
The browser connects through your server proxy, which reads FAL_KEY from the server environment. Never put that key in browser code.
API Key#
Protect your API Key
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.
3. Schema#
Media#
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.
Send
No send media tracks.
Receive
{
"width": 1280,
"height": 704,
"frameRate": 16
}Client messages#
Movement
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[]stringValues: "forward", "left", "backward", "right", "look_up", "look_left", "look_down", "look_right"
activatedstring[]maxItems: 8
stringValues: "forward", "left", "backward", "right", "look_up", "look_left", "look_down", "look_right"
typestring* requiredConstant: "commands"
{
"active": [
"forward"
],
"activated": [],
"type": "commands"
}Customize world
type: "configure"payload: objectOptionally replace the automatic default world, once per connection.
Await configured before submitting more controls. Use reset for
subsequent restarts. Image URL takes precedence over the preset.
presetstring | nullChoose a built-in world, or provide a starting image below.
Default: null
At most 100 characters when not null
promptstring | nullOptionally replace the starting world's description.
Default: null
At most 2000 characters when not null
image_urlstring | nullUse your own image instead of a built-in world.
Default: null
At most 4096 characters when not null
typestring* requiredConstant: "configure"
Explore sunlit temple ruins, towering columns, and paths reclaimed by vines.
{
"preset": "example",
"type": "configure"
}Client keys message
type: "keys"payload: objectLegacy physical-key report retained for existing WMA clients.
pressedstring[]maxItems: 32
stringactivatedstring[]maxItems: 32
stringtypestring* requiredConstant: "keys"
{
"type": "keys"
}Client ping message
type: "ping"payload: objectLatency probe; the server echoes ts back in a pong payload.
tsnumber | nullDefault: null
typestring* requiredConstant: "ping"
{
"type": "ping"
}Update world
type: "prompt"payload: objectpromptstring* requiredChange the scene's description while continuing this world.
At least 1 character · At most 2000 characters
typestring* requiredConstant: "prompt"
{
"prompt": "A realistic outdoor world scene with a navigable path, natural lighting, detailed ground texture, and stable forward motion.",
"type": "prompt"
}Restart world
type: "reset"payload: objectpresetstring | nullChoose a built-in world, or provide a starting image below.
Default: null
At most 100 characters when not null
promptstring | nullOptionally replace the starting world's description.
Default: null
At most 2000 characters when not null
image_urlstring | nullUse your own image instead of a built-in world.
Default: null
At most 4096 characters when not null
typestring* requiredConstant: "reset"
Explore sunlit temple ruins, towering columns, and paths reclaimed by vines.
{
"preset": "example",
"type": "reset"
}End session
type: "stop"payload: objectAdditional properties: not allowed
typestring* requiredConstant: "stop"
{
"type": "stop"
}Server messages#
Server configured message
type: "configured"payload: objecttypestring* requiredConstant: "configured"
{
"type": "configured"
}Server error message
type: "error"payload: objecterrorstring* requiredretryableboolean | nullWhether retrying unchanged input may succeed; absent if unspecified.
Default: null
typestring* requiredConstant: "error"
{
"error": "string",
"type": "error"
}Server pong message
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
tsnumber | nullDefault: null
server_tsnumber | nullDefault: null
typestring* requiredConstant: "pong"
{
"type": "pong"
}Server prompt_applied message
type: "prompt_applied"payload: objecttypestring* requiredConstant: "prompt_applied"
{
"type": "prompt_applied"
}Server reset_applied message
type: "reset_applied"payload: objecttypestring* requiredConstant: "reset_applied"
{
"type": "reset_applied"
}Server session_info message
type: "session_info"payload: objectturnboolean* requireddefault_promptstring* requiredresolutionobject* requiredwidthinteger* requiredheightinteger* requiredmodel_idstring* requiredtypestring* requiredConstant: "session_info"
conflict_groupsstring[][]* requiredstring[]stringmodel_revisionstring* requiredpresetsobject[]* requiredobjectnamestring* requiredpromptstring* requiredkey_orderstring[]* requiredstringturn_statusstring* requiredframes_per_blockinteger* requiredframes_per_secondnumber* requiredmax_blocksinteger* required{
"turn": true,
"default_prompt": "string",
"resolution": {
"width": 0,
"height": 0
},
"model_id": "string",
"type": "session_info",
"conflict_groups": [],
"model_revision": "string",
"presets": [],
"key_order": [],
"turn_status": "string",
"frames_per_block": 0,
"frames_per_second": 0,
"max_blocks": 0
}Server stats message
type: "stats"payload: objectblock_secondsnumber* requiredqueue_depthinteger* requiredgeneration_fpsnumber* requiredqueue_age_ms_maxnumber* requiredtypestring* requiredConstant: "stats"
gpu_wait_fracnumber* requiredpace_sleep_ms_avgnumber* requiredqueue_age_ms_p50number* requiredblock_indexinteger* required{
"block_seconds": 0,
"queue_depth": 0,
"generation_fps": 0,
"queue_age_ms_max": 0,
"type": "stats",
"gpu_wait_frac": 0,
"pace_sleep_ms_avg": 0,
"queue_age_ms_p50": 0,
"block_index": 0
}Server stream_exhausted message
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"
}