00:00 / 00:00

Persona

Plugin API

Persona runs a local WebSocket server that lets other applications control Persona: switch scenes, add and place layers, toggle expressions, play motions, play lip-synced speech, read tracking status, and write model parameters directly

It is off by default. Turn it on under Settings → Plugin API, create a key, and paste the token into your plugin

Turning It On

ControlEffect
Enable APIStarts the server. Nothing connects without a key
PortDefault 25034
Allow Access on Local NetworkBinds beyond loopback, so a phone or another machine can reach it

With local network access on, Persona shows the addresses you can use to connect from a browser

If the port is already taken, the section shows the error instead of failing silently

API Keys

A key is required — an unauthenticated connection is refused. Create Key names one and shows its token once in full; the copy button on its row reveals it again at any time

ActionEffect
Copy TokenCopy the full token to the clipboard
Rename KeyRename it in place
Revoke KeyDelete it. Sessions using it are closed and told not to reconnect

Each key row also reports Connected, Never used, or a relative Last used … time. Once a client identifies itself, the row remembers its latest name and version, so the key remains recognizable while no client is connected

Tokens are held in the operating system's secure storage. On a machine where that is unavailable, key creation fails with an explanation rather than writing a token to a plain file

Plugin Folders

A plugin can ask Persona to register a model or asset file, so it can then load it. That only works for files inside a folder you added under Plugin Folders — any other path is refused with forbidden-path

Connected Clients

Every open session is listed while the server is running, by the name it declared, with its version, developer, which key it used, and where it connected from. Browser clients show their page origin

A client that never identifies itself is listed as Unidentified client and works exactly the same. The disconnect button on a row closes that session and instructs the client to stop reconnecting

The Web Console

persona.laplace.live is a browser console built on this API. As a first-party “plugin”, use it to check the connection or control Persona from a phone or tablet during a stream

Enter the host, port and key to control scenes, layers, expressions, motions, effects, the camera, lighting, shortcuts, tracking and settings. The layout follows the desktop Control Panel: layers appear on the left, with Stage, Tracking, Shortcuts and Settings below them. The selected item’s details appear on the right when space allows. Each section has a URL you can bookmark. Select a model to access its expressions, motions and Parameters bindings, or an effect in the layer list to adjust its parameters. Controls are disabled when the selected instance does not support them

The Add to Scene button below the layer list opens the Inventory, populated from Persona’s registry through the API. To register a file from the console, enter its path on the desktop machine. The file must be inside a configured Plugin Folder

The scene assets include separate Environment Maps and LUTs tabs. Click an asset to apply it to the current scene; a toast confirms the change. Animations has no separate tab because the API does not support direct clip playback. Register a clip using the kind picker on the All tab, then use Choose… to set it as a layer’s idle animation

Select the scene and Lighting lists the lights a 3D object or environment carries, with the same switch and intensity slider as the desktop. Loaded asset dimensions and glTF extensions appear there too

You can store the key in the browser without encryption or keep it for the current session only. Control requests go directly to Persona on your machine or local network

The following features are available only in the desktop application: the updater, API keys, the tray icon, window bounds, native file pickers, on-stage handles and shortcut recording. App and scene shortcuts are created on the desktop too — the console can only fire them

Stream Deck

The Stream Deck plugin is the other first-party client of this API, letting you use physical keys to switch models and scenes, trigger hotkeys, expressions and motions, and move or scale the avatar

The SDK

@laplace.live/persona-sdk is a typed client that works in Node, Bun, browsers and OBS browser sources

import { PersonaClient } from "@laplace.live/persona-sdk";

const persona = new PersonaClient({ token: "sk-lp-v1-…" });
await persona.connect();

await persona.call("expression.toggle", { name: "Smile" });
persona.on("motion.started", (m) => console.log("playing", m.group));

Every method is typed through one shared table, so a call's request and response shapes follow from its name. The SDK reference covers installation, client options, authentication, parameter leases and every exported type

Events

A session receives only the events it subscribes to. The SDK restores subscriptions after reconnecting

EventFires when
scene.changedAny scene mutation: create, delete, rename, activate, or an edit
scene.loadingA scene apply or pipeline warm-up starts and finishes
settings.changedApplication settings changed
instance.loadedA model instance finished loading on stage
selection.changedThe editing selection moved, from any source
expression.changedThe active expression set changed
expression.persistenceA model's Remember Expressions flag was toggled
motion.startedA motion began playing
motion.endedA motion finished
speech.startedA speech.play utterance began
speech.endedIt finished, was stopped, or was superseded
hotkey.stateThe hotkey configuration changed
shortcut.stateThe global shortcut list, its names, or its OS registrations changed
tracking.statusFace or pose tracking status changed
registry.changedA model or asset was registered — re-read the matching list

Methods

Model-scoped methods take an optional instanceId and default to the scene's primary model

GroupMethods
scene.*list get activate create duplicate rename delete setShortcut patch inspect
instance.*list get add setModel remove setVisible setTrackingSources reorder setPrimary setPlacement setMToon setIdle setControllerMovement info
binding.*get preview set release inputs override
controller.*state rename setDeadZone remove assign
selection.*get set
object.*add rename setContent setSpace setPlacement attach setLightOverrides anchors
expression.*list active toggle getPersistence setPersistence
motion.*list play playing
speech.*play stop
hotkey.*list set trigger
shortcut.*list trigger
model.* / asset.*list and register for each, plus registry.thumbnail
settings.*get patch
tracking.* / pose.*tracking.addSource tracking.updateSource tracking.removeSource, setEnabled, setSource, plus pose.setPort and tracking.status
stage.*resetTransform resetCamera
app.* / session.*app.info app.localAddresses app.stats, and session.identify
storage.*get set delete list
param.*inject release

Capabilities

Check the application and instance capability lists to determine which features are supported, rather than comparing version numbers

The application's own is reported in hello and by app.info — currently storage, speech, shortcuts, controllers, model-editing and asset-inspection. Each instance carries its own under capabilities in instance.list, and a method no capability covers is refused with unsupported-for-format

The last three each unlock a set of shared-editor methods. controllers covers controller.* — reading connected devices and saved profiles, renaming, setting a dead zone, removing one, and starting an assignment — plus the controller.enabled master switch through settings.patch; the devices are the ones attached to the desktop, not to the browser's own computer. model-editing covers binding.* (reading, previewing, committing and releasing a model's parameter bindings, and short-lived input overrides) along with instance.setBreath. asset-inspection covers scene.inspect and registry.thumbnail

instance.setControllerMovement merges an instance's controller movement configuration: enabled, slot (a saved profile number), moveSpeed, turnSpeed, smoothing, and on VRM also walkEnabled, walkClip, walkSpeed and faceMovement. Out-of-range values are reset to defaults or clamped to the allowed range. Calls targeting objects return unsupported-for-format. walkClip accepts the same references as instance.setIdle

Tracking Sources

Persona can run several configured tracking sources at once. settings.tracking.sources lists them, while tracking.addSource, tracking.updateSource and tracking.removeSource manage the list. instance.setTrackingSources binds an instance's face and pose channels (faceSourceId and poseSourceId) by source id. null stops tracking for that channel, and an unknown id behaves the same. New instances start on each channel's default source; one source may drive several instances

Face sources use persona-ios, ifacialmocap or vts-ios, with an optional phoneIp to pin one sender. Body sources use vmc or mocopi. A port belongs to vmc, mocopi and ifacialmocap — one already held by another source is rejected, except between ifacialmocap sources, which share a socket; persona-ios and vts-ios have their ports fixed by the protocol. The retired vts-ios-native kind is rejected on the wire

tracking.status returns the aggregate face and pose state plus a sources map with every configured source's own status; the tracking.status event carries the same shape

The legacy single-source methods still address the first source in their channel: tracking.setSource changes its kind, pose.setSource creates a VMC source when needed and returns its port, and pose.setPort updates that port. tracking.setEnabled and pose.setEnabled remain the channel-wide master switches

Speech

speech.play plays audio and drives the mouth from its loudness. It works on instances advertising the speech capability — which follows the rendering engine rather than the format, so check the instance's capabilities. Today Live2D on the default engine advertises it; the legacy WebGL mode does not

ParameterMeaning
urlhttps:, http: (a local TTS bridge), or an inline data:audio/* payload
volumeOptional, 0..1
instanceIdOptional, defaults to the scene's primary model

One utterance per model: a new play supersedes the current one. The call returning means playback started; invalid-state means it did not. speech.started and speech.ended bracket the utterance, and speech.stop ends it early

Global Shortcuts

A build carrying the shortcuts capability can list and run the application's app shortcuts — macros that apply several stage changes together

MethodEffect
shortcut.listList every shortcut
shortcut.triggerRun a shortcut by its ID

Action payloads stay app-side. What the list gives you is id, title, accelerator, registered, and an actionKinds array — enough to draw a row of buttons without knowing what each action does. When title is null, derive a label from actionKinds; the SDK's shortcutLabel does exactly that

accelerator can also be null. That is not an error, only a shortcut with no combination bound — shortcut.trigger still fires it. Whenever the list, its names, or its OS registrations change, shortcut.state delivers the whole list again

Plugin Storage

storage.* gives a plugin durable key–value storage namespaced by API key — the key is the plugin's identity, every session on it shares the same data, and revoking the key deletes it. Values are arbitrary JSON

MethodEffect
storage.getRead a key. An unset key returns value: null
storage.setWrite a key
storage.deleteDelete a key. An absent key still succeeds
storage.listList every key, sorted

The limits are 128 characters per key, 64 KB per serialized value, and 256 keys per API key. A storage.set call that would exceed the key count limit returns invalid-state

Errors

persona.call throws PersonaApiError with a string code:

CodeMeaning
parse-errorThe frame is not valid JSON
invalid-requestThe envelope was malformed
unknown-methodThis build has no such method
invalid-paramsThe parameters failed validation
not-foundA referenced id resolves to nothing
unsupported-for-formatThe instance's format cannot do this — MToon on a Live2D model, say
conflictAnother session holds the parameter lease
renderer-unavailableThe stage could not answer — no window, or a reloading renderer
forbidden-pathThe path is outside every configured plugin folder
invalid-stateThe operation cannot complete: deleting the last scene, exceeding a storage quota, or failing to start audio
internalEverything else

The code set is part of the protocol: clients drop error frames carrying an unknown code, so new codes only arrive with a protocol bump and new failures reuse these

Disconnects

The client automatically reconnects after an unexpected disconnect. The following close codes stop automatic reconnection to respect key revocation or a manual disconnect:

CodeConstantCause
4001CLOSE_KEY_REVOKEDThe key was revoked
4002CLOSE_FORCE_DISCONNECTEDThe user disconnected the session

Both settle the client in the closed state. If onClose is supplied, a terminal close is delivered there and onWarning is skipped; without onClose, it falls back to onWarning. 1001 is not one of them — it means the server is stopping, so reconnect once it returns

Revoking a key immediately discards pending frames and closes connections using that key with 4001. Requests still waiting for the stage cannot resume writing. Actions already sent to the stage may finish, but their responses are discarded

Version Mismatches

The protocol carries a version, and the SDK warns when it does not match the connected build. Subscribing to an event an older Persona does not know is not fatal — the SDK falls back to subscribing one event at a time, keeps the ones that work, and warns by name about the rest

Last updated on September 10, 2026

Tech otakus destroy the world