Rostodocs Open the Console

Docs

Living avatars,
in a few lines.

One consenting selfie becomes an avatar that breathes, speaks and feels, animated live in any browser, with no AI at runtime.

Embed an avatar

Already have a public token? This is the whole integration: drop it anywhere. The token is a capability, so rotating it revokes every embed at once.

<div id="rosto"></div>
<script src="https://studio.rosto.ai/rig/avatar-rig.js"></script>
<script>
  fetch("https://studio.rosto.ai/pub/PUBLIC_TOKEN/manifest.json").then(r => r.json()).then(m => {
    const rig = AvatarRig.mount(document.getElementById("rosto"), m, {size: 320});
    rig.setState("idle");            // idle · listening · thinking · speaking
    // rig.speak("Hello!"); rig.express("genuine-joy"); rig.setMood("warm");
  });
</script>

Create one via the API

Authenticate with x-api-key (get a key from the Console by redeeming an invite). Consent is required. Your photo is kept encrypted only until you accept the avatar, then deleted.

# 1 — create an avatar (get a key from the Console: redeem an invite)
curl -sX POST https://studio.rosto.ai/v1/avatars \
  -H "x-api-key: $ROSTO_KEY" -H "content-type: application/json" \
  -d '{"image_base64": "'"$(base64 -i selfie.jpg)"'", "mime_type": "image/jpeg", "consent_attested": true}'
# -> { "avatar": { "id": "...", "public_token": "TOKEN" } }

# 2 — build the expression set
curl -sX POST https://studio.rosto.ai/v1/avatars/$AVATAR_ID/frames -H "x-api-key: $ROSTO_KEY"

# 3 — the runtime loads the public manifest (no key; the token is the capability)
curl -s https://studio.rosto.ai/pub/$TOKEN/manifest.json

Drive the rig

The avatar animates in the browser, with no model calls at runtime. Command it directly, or hand it a JSON command stream and let an LLM drive it automatically.

rig.setState("listening");        // idle · listening · thinking · speaking · acknowledging\nrig.setMood("warm");\nrig.express("genuine-joy");        // the manifest lists what this avatar supports\nrig.speak("Great to meet you.");   // viseme lip-sync\nrig.command({ state: "speaking", emotion: "curious", say: "Tell me more?" });

Or skip the animator vocabulary entirely. rig.direct(cue) speaks in the terms your app already knows — conversational intents. Your logic detects the situation; one call performs it (mood + state + emotion beat + head language + speech, choreographed):

rig.direct("await");                                  // friendly, present, waiting\n// the feedback turned negative:\nrig.direct({ intent: "empathize", say: "I completely understand. We will fix this." });\n// the joke landed:\nrig.direct({ intent: "laugh", say: "Okay, that was a good one." });\n// with your TTS audio: the voice locks to the lips (starts on its play event)\nrig.direct({ intent: "reassure", say: "All fixed.", audio: new Audio(url) });\n// AvatarRig.INTENTS lists all of them: greet · await · attend · ponder · empathize ·\n// apologize · reassure · laugh · celebrate · agree · disagree · surprised · curious · farewell

Full vocabulary and dynamics: the control protocol.

Adding voice

Rosto animates the face; it does not include a voice. You bring the audio, which keeps you free to use any provider (or none). Two ways to run it:

// THE RULE: lips start WITH the audio, never with the request.
// TTS generation takes seconds — call rig.speak too early and the voice
// lands on a face that already finished talking.

// Free, client-side — perfect for a demo:
const say = (t) => {
  const u = new SpeechSynthesisUtterance(t);
  u.onstart = () => rig.speak(t);        // lips start exactly when the voice does
  speechSynthesis.speak(u);
};

// Production — fetch your TTS first, then let speakWith handle the sync:
async function speak(text) {
  const url = await myTTS(text);         // your provider returns an audio URL
  rig.speakWith(text, new Audio(url));   // starts playback, lips on its 'play'
}                                        // event; still talks if audio is blocked

// speakWith gives you four sync layers automatically:
//   1. START lock  — lips begin on the audio's 'play' event, never earlier
//   2. LENGTH lock — the viseme timeline is paced to the real audio duration
//   3. LIVE follow — an analyser tracks the actual sound: pauses close the
//      mouth, stressed syllables open it (fails open — cadence — if the
//      browser blocks the audio tap)
//   4. ALIGNMENT  — the letter clock pauses with the voice and resumes on the
//      next word, re-anchoring the mouth-shape sequence at every pause; when
//      the audio ends, so do the lips
// Perfect per-phoneme sync (providers with character timestamps, e.g.
// ElevenLabs): drive rig.setViseme(...) from the timestamp stream instead.

For production, any text-to-speech works (ElevenLabs, OpenAI, Google, Azure). Start rig.speak(text) together with playback for the tightest sync; it is provider-agnostic.

Connect an agent (MCP)

Rosto ships a remote MCP server so an agent can go from selfie to embedded avatar on its own.

// Point any MCP client at the Rosto server (JSON-RPC 2.0, streamable HTTP):
{ "mcpServers": { "rosto": { "url": "https://mcp.rosto.ai/mcp" } } }

// Then the agent walks:
//   start_free(invite_code) -> create_avatar(api_key, image_base64, mime_type, consent)
//   -> build_frames(api_key, avatar_id) -> get_embed_guide(public_token)

Reference

Every avatar is consented; the photo is kept encrypted only until it's accepted, then permanently deleted; generated media is provenance-stamped; tokens are revocable to the CDN edge.

a selfie becomes a living, feeling face