The SDK surface follows a small set of rules so it stays predictable and hard to misuse. Knowing them makes the whole API guessable.Documentation Index
Fetch the complete documentation index at: https://docs.mascot.bot/llms.txt
Use this file to discover all available pages before exploring further.
1. Events vs. callbacks
- Lifecycle / multi-fire → an emitter.
client.on("ready" | "refused" | "error" | "refresh", fn)returns an unsubscribe function. Use it for things that happen repeatedly or that multiple listeners care about. - One-shot wiring / per-item telemetry → an option callback. e.g.
useLipsyncStream({ onFrame }). Use it to configure one thing at construction.
onReady prop when
client.on("ready") exists).
2. Options object, not positional
New hooks and functions take a single options object — e.g.useLoadRive({ stateMachineName, ... }). No public function takes multiple
positional arguments where an options object would do.
3. Async is honest
If a value is computed off-thread (worker, network), the method isasync
and stays async — client.diagnostics(), session.pushWindow(). Nothing
hides a Promise behind a sync-looking API, so you always know where to
await.
4. Error taxonomy
Five classes —LipsyncError (base) plus License / Network / Engine /
RefusedError — mapped to failure domains. You branch on .code, not the
subclass. A new failure that is not a new domain reuses LipsyncError with a
new .code (e.g. bad_timeline) rather than adding a subclass per code.
Every code is registered in Error codes.
5. Serialized-format versioning
Anything you can persist and feed back later carries an explicitversion and
a validating parser that rejects mismatches loudly. VisemeTimeline has
version: VISEME_TIMELINE_VERSION plus frameMs, and parseTimeline throws
LipsyncError("bad_timeline", …) on any incompatibility. The version is
bumped on any breaking shape or semantics change; an old shape is never
silently accepted. Always load persisted data through the validating parser,
never JSON.parse alone. See
the timeline model.
6. Module boundaries
Import the narrowest entry point for the job:| Entry | Contains | Excludes |
|---|---|---|
@mascotbot/core | Engine, VisemeTimeline + helpers, createPCMStreamPlayer | No Rive, no React |
@mascotbot/core/rive | MascotPlayback, getRiveInputs, hasRiveInput | No React; @rive-app/webgl2 is an optional peer |
@mascotbot/react | MascotProvider, useMascot, useProcessAudio | No Rive |
@mascotbot/react/rive | The React Rive layer | — |
7. The SDK stays out of your Rive instance
The SDK writes exactly three input families — mouth visemes,is_speaking,
stress — and nothing else. Every other Rive capability is reached directly
on the raw instance. This is a hard contract, documented in
Rive co-existence: the SDK never wraps, gates,
proxies, or constrains Rive.
Next
Error codes
The full code matrix.
Rive co-existence
The Rive ownership contract.
Migration
The 0.2.x symbol map.