# Rosto
> Turn one consenting selfie into a living, emotionally-rich avatar that animates
> in any browser — breathing, blinking, speaking (viseme lip-sync) and expressing
> a full range of genuine emotion, with no AI at runtime.
Rosto is an API plus an embeddable browser rig. You create an avatar once (a
model stylizes the photo and builds a QA-gated FACS expression set), then the
served runtime animates it live in any browser. There are no model calls while
the avatar plays.
## Start here
- Console (bring a face to life in a browser): https://app.rosto.ai
- API base: https://studio.rosto.ai
- MCP server for agents (JSON-RPC, streamable HTTP): https://mcp.rosto.ai/mcp
- Quickstart / developer docs: https://studio.rosto.ai/docs
- OpenAPI: https://studio.rosto.ai/openapi.json (interactive explorer: https://studio.rosto.ai/api-explorer)
- Avatar Package spec (the portable format, self-host + agent integration): https://studio.rosto.ai/docs-site/avatar-package
## Embed an existing avatar (a public token is the capability)
## Create via the API
Authenticate with `x-api-key`. Get a key from the Console — sign in with
Google, GitHub or Discord (or redeem an invite code, if you have one).
1. POST https://studio.rosto.ai/v1/avatars
body: {"image_base64": "", "mime_type": "image/jpeg", "consent_attested": true}
-> {"avatar": {"id": "...", "public_token": "..."}}
(consent_attested MUST be true; the photo is kept encrypted only until the avatar is accepted, then deleted)
2. POST https://studio.rosto.ai/v1/avatars//frames (build the expression set; may be async)
3. GET https://studio.rosto.ai/pub//manifest.json (the runtime loads this)
4. POST https://studio.rosto.ai/v1/avatars//own (buy it: one credit. Making an avatar and hosting it here are FREE; owning it unlocks the download)
5. GET https://studio.rosto.ai/v1/avatars//export (download a standalone, self-hostable ZIP: portable manifest + base + frames + rig + runnable index.html + README + AGENTS.md; requires manage scope + an OWNED avatar)
6. GET https://studio.rosto.ai/v1/avatars//stickers?captions=matched (eight looping reactions as GIF + 512px WebP, rendered from the frames at no model cost)
7. GET https://studio.rosto.ai/pub//lottie/.json (the same eight as Lottie 5.5 — one image layer per page, STEP-hold opacity; `?mode=natural` for the quieter register)
## Drive the rig (no AI at runtime)
ONE CALL PER LINE. The word is what the line IS — a dialogue act, not a sentiment:
rig.direct({ intent, say, audio, visemes }) — mood + state + expression + head
+ lip-sync, together
visemes come from POST https://studio.rosto.ai/visemes {text} -> AvatarRig.trackFromAudio(buf, units).
Without them the mouth is derived from SPELLING, which scores no better than
animating to a different line's audio — that is the demo path, not production.
rig.direct("attend") — between turns; "ponder" while you work
INTENTS (14): greet await attend ponder empathize apologize reassure laugh celebrate agree disagree surprised curious farewell
If you are an LLM writing the reply, return the intent in the SAME structured
output — one enum field, no extra call, no added latency. Pick the act your
sentence PERFORMS, not its sentiment and not the user's mood: "I'm sorry your
package broke" is `empathize`, not `sad`. Most turns are attend/agree/empathize/
ponder; forcing a vivid one every turn is the fastest way to look fake.
An intent is a COMPLETE performance: the same intent gives the same face
whatever preceded it. Never compose one from smaller commands, never clear
state first, never stack a mood to correct for the last turn.
Lower-level vocabulary, for what an intent cannot say:
rig.setState("idle"|"listening"|"thinking"|"speaking"|"acknowledging"|"holding")
rig.setMood(mood); rig.express(emotion)
rig.direct({say, audio, visemes}) // visemes: POST /visemes -> trackFromAudio
rig.follow() // gaze follows the cursor (scroll on touch); unfollow() detaches
rig.command({...}) / rig.play([...]) — a JSON command stream can drive it automatically
The RIG exports the vocabulary: AvatarRig.STATES / .EMOTIONS / .INTENTS. The
manifest lists the FRAMES this avatar has — the pages the rig composites, not
the emotions it composes from them. A missing frame dims one channel; the
emotion still plays.
Full standard: https://studio.rosto.ai/docs-site#drive
## MCP tools (for agents)
POST https://mcp.rosto.ai/mcp (JSON-RPC 2.0)
- start_free() -> user_code + approve_url (the user approves in one tap, then
call start_free(user_code) -> api_key; start_free(invite_code) still works)
- create_avatar(api_key, image_base64, mime_type, consent) -> avatar_id, public_token
- build_frames(api_key, avatar_id)
- preview(public_token)
- get_embed_guide(public_token)
- own(api_key, avatar_id) -> buy the avatar; the downloadable bundle unlocks
- get_credits(api_key) / buy_credits(api_key)
## Invariants (always true)
- Making an avatar and hosting it on Rosto (with the badge) are free; the single
charge is OWNING one, which unlocks the self-hostable download.
- Consent is required for every avatar; the consent record is kept even after deletion.
- The uploaded photo is kept encrypted only until the avatar is accepted, then permanently deleted.
- No AI at runtime: the playing avatar is a pure browser rig.
- Public tokens are revocable to the CDN edge (rotate or delete).
- AI-generated media is provenance-stamped (IPTC digitalSourceType: trainedAlgorithmicMedia).